diff options
Diffstat (limited to 'modules/gdscript')
50 files changed, 22798 insertions, 20135 deletions
diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index e58a1d8edc..5c8cbdf869 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -17,3 +17,7 @@ if env["tools"]: # Using a define in the disabled case, to avoid having an extra define # in regular builds where all modules are enabled. env_gdscript.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) + +if env["tests"]: + env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"]) + env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py index 6fc227e7f5..61ce6185a5 100644 --- a/modules/gdscript/config.py +++ b/modules/gdscript/config.py @@ -10,7 +10,6 @@ def get_doc_classes(): return [ "@GDScript", "GDScript", - "GDScriptFunctionState", ] diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index be159b6407..9e974b6fdc 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -7,6 +7,7 @@ List of core built-in GDScript functions. Math functions and other utilities. Everything else is provided by objects. (Keywords: builtin, built in, global functions.) </description> <tutorials> + <link title="Random number generation">https://docs.godotengine.org/en/latest/tutorials/math/random_number_generation.html</link> </tutorials> <methods> <method name="Color8"> @@ -31,60 +32,6 @@ [/codeblock] </description> </method> - <method name="ColorN"> - <return type="Color"> - </return> - <argument index="0" name="name" type="String"> - </argument> - <argument index="1" name="alpha" type="float" default="1.0"> - </argument> - <description> - Returns a color according to the standardized [code]name[/code] with [code]alpha[/code] ranging from 0 to 1. - [codeblock] - red = ColorN("red", 1) - [/codeblock] - Supported color names are the same as the constants defined in [Color]. - </description> - </method> - <method name="abs"> - <return type="float"> - </return> - <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). - [codeblock] - # a is 1 - a = abs(-1) - [/codeblock] - </description> - </method> - <method name="acos"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the arc cosine of [code]s[/code] in radians. Use to get the angle of cosine [code]s[/code]. - [codeblock] - # c is 0.523599 or 30 degrees if converted with rad2deg(s) - c = acos(0.866025) - [/codeblock] - </description> - </method> - <method name="asin"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the arc sine of [code]s[/code] in radians. Use to get the angle of sine [code]s[/code]. - [codeblock] - # s is 0.523599 or 30 degrees if converted with rad2deg(s) - s = asin(0.5) - [/codeblock] - </description> - </method> <method name="assert"> <return type="void"> </return> @@ -93,85 +40,23 @@ <argument index="1" name="message" type="String" default=""""> </argument> <description> - Asserts that the [code]condition[/code] is [code]true[/code]. If the [code]condition[/code] is [code]false[/code], an error is generated and the program is halted until you resume it. Only executes in debug builds, or when running the game from the editor. Use it for debugging purposes, to make sure a statement is [code]true[/code] during development. + Asserts that the [code]condition[/code] is [code]true[/code]. If the [code]condition[/code] is [code]false[/code], an error is generated. When running from the editor, the running project will also be paused until you resume it. This can be used as a stronger form of [method @GlobalScope.push_error] for reporting errors to project developers or add-on users. + [b]Note:[/b] For performance reasons, the code inside [method assert] is only executed in debug builds or when running the project from the editor. Don't include code that has side effects in an [method assert] call. Otherwise, the project will behave differently when exported in release mode. The optional [code]message[/code] argument, if given, is shown in addition to the generic "Assertion failed" message. You can use this to provide additional details about why the assertion failed. [codeblock] - # Imagine we always want speed to be between 0 and 20 - speed = -10 + # Imagine we always want speed to be between 0 and 20. + var speed = -10 assert(speed < 20) # True, the program will continue assert(speed >= 0) # False, the program will stop - assert(speed >= 0 && speed < 20) # You can also combine the two conditional statements in one check + assert(speed >= 0 and speed < 20) # You can also combine the two conditional statements in one check assert(speed < 20, "speed = %f, but the speed limit is 20" % speed) # Show a message with clarifying details [/codeblock] </description> </method> - <method name="atan"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </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. - [codeblock] - a = atan(0.5) # a is 0.463648 - [/codeblock] - </description> - </method> - <method name="atan2"> - <return type="float"> - </return> - <argument index="0" name="y" type="float"> - </argument> - <argument index="1" name="x" type="float"> - </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. - [codeblock] - a = atan2(0, -1) # a is 3.141593 - [/codeblock] - </description> - </method> - <method name="bytes2var"> - <return type="Variant"> - </return> - <argument index="0" name="bytes" type="PackedByteArray"> - </argument> - <argument index="1" name="allow_objects" type="bool" default="false"> - </argument> - <description> - Decodes a byte array back to a value. When [code]allow_objects[/code] is [code]true[/code] decoding objects is allowed. - [b]WARNING:[/b] Deserialized object can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats (remote code execution). - </description> - </method> - <method name="cartesian2polar"> - <return type="Vector2"> - </return> - <argument index="0" name="x" type="float"> - </argument> - <argument index="1" name="y" type="float"> - </argument> - <description> - Converts a 2D point expressed in the cartesian coordinate system (X and Y axis) to the polar coordinate system (a distance from the origin and an angle). - </description> - </method> - <method name="ceil"> - <return type="float"> - </return> - <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]. - [codeblock] - i = ceil(1.45) # i is 2 - i = ceil(1.001) # i is 2 - [/codeblock] - </description> - </method> <method name="char"> <return type="String"> </return> - <argument index="0" name="code" type="int"> + <argument index="0" name="char" type="int"> </argument> <description> Returns a character as a String of the given Unicode code point (which is compatible with ASCII code). @@ -180,29 +65,6 @@ a = char(65 + 32) # a is "a" a = char(8364) # a is "€" [/codeblock] - This is the inverse of [method ord]. - </description> - </method> - <method name="clamp"> - <return type="float"> - </return> - <argument index="0" name="value" type="float"> - </argument> - <argument index="1" name="min" type="float"> - </argument> - <argument index="2" name="max" type="float"> - </argument> - <description> - Clamps [code]value[/code] and returns a value not less than [code]min[/code] and not more than [code]max[/code]. - [codeblock] - speed = 1000 - # a is 20 - a = clamp(speed, 1, 20) - - speed = -10 - # a is 1 - a = clamp(speed, 1, 20) - [/codeblock] </description> </method> <method name="convert"> @@ -224,185 +86,13 @@ [/codeblock] </description> </method> - <method name="cos"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the cosine of angle [code]s[/code] in radians. - [codeblock] - # Prints 1 then -1 - print(cos(PI * 2)) - print(cos(PI)) - [/codeblock] - </description> - </method> - <method name="cosh"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the hyperbolic cosine of [code]s[/code] in radians. - [codeblock] - # Prints 1.543081 - print(cosh(1)) - [/codeblock] - </description> - </method> - <method name="db2linear"> - <return type="float"> - </return> - <argument index="0" name="db" type="float"> - </argument> - <description> - Converts from decibels to linear energy (audio). - </description> - </method> - <method name="dectime"> - <return type="float"> - </return> - <argument index="0" name="value" type="float"> - </argument> - <argument index="1" name="amount" type="float"> - </argument> - <argument index="2" name="step" type="float"> - </argument> - <description> - Returns the result of [code]value[/code] decreased by [code]step[/code] * [code]amount[/code]. - [codeblock] - # a = 59 - a = dectime(60, 10, 0.1)) - [/codeblock] - </description> - </method> - <method name="deg2rad"> - <return type="float"> - </return> - <argument index="0" name="deg" type="float"> - </argument> - <description> - Returns degrees converted to radians. - [codeblock] - # r is 3.141593 - r = deg2rad(180) - [/codeblock] - </description> - </method> <method name="dict2inst"> <return type="Object"> </return> - <argument index="0" name="dict" type="Dictionary"> + <argument index="0" name="dictionary" type="Dictionary"> </argument> <description> - Converts a previously converted instance to a dictionary, back into an instance. Useful for deserializing. - </description> - </method> - <method name="ease"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <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. - </description> - </method> - <method name="exp"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </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. - For exponents to other bases use the method [method pow]. - [codeblock] - a = exp(2) # Approximately 7.39 - [/codeblock] - </description> - </method> - <method name="floor"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Rounds [code]s[/code] to the closest smaller integer and returns it. - [codeblock] - # a is 2.0 - a = floor(2.99) - # a is -3.0 - a = floor(-2.99) - [/codeblock] - [b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly. - </description> - </method> - <method name="fmod"> - <return type="float"> - </return> - <argument index="0" name="a" type="float"> - </argument> - <argument index="1" name="b" type="float"> - </argument> - <description> - Returns the floating-point remainder of [code]a/b[/code], keeping the sign of [code]a[/code]. - [codeblock] - # Remainder is 1.5 - var remainder = fmod(7, 5.5) - [/codeblock] - For the integer remainder operation, use the % operator. - </description> - </method> - <method name="fposmod"> - <return type="float"> - </return> - <argument index="0" name="a" type="float"> - </argument> - <argument index="1" name="b" type="float"> - </argument> - <description> - Returns the floating-point modulus of [code]a/b[/code] that wraps equally in positive and negative. - [codeblock] - var i = -6 - while i < 5: - prints(i, fposmod(i, 3)) - i += 1 - [/codeblock] - Produces: - [codeblock] - -6 0 - -5 1 - -4 2 - -3 0 - -2 1 - -1 2 - 0 0 - 1 1 - 2 2 - 3 0 - 4 1 - [/codeblock] - </description> - </method> - <method name="funcref"> - <return type="FuncRef"> - </return> - <argument index="0" name="instance" type="Object"> - </argument> - <argument index="1" name="funcname" type="String"> - </argument> - <description> - Returns a reference to the specified function [code]funcname[/code] in the [code]instance[/code] node. As functions aren't first-class objects in GDscript, use [code]funcref[/code] to store a [FuncRef] in a variable and call it later. - [codeblock] - func foo(): - return("bar") - - a = funcref(self, "foo") - print(a.call_func()) # Prints bar - [/codeblock] + Converts a dictionary (previously created with [method inst2dict]) back to an instance. Useful for deserializing. </description> </method> <method name="get_stack"> @@ -426,22 +116,10 @@ [/codeblock] </description> </method> - <method name="hash"> - <return type="int"> - </return> - <argument index="0" name="var" type="Variant"> - </argument> - <description> - Returns the integer hash of the variable passed. - [codeblock] - print(hash("a")) # Prints 177670 - [/codeblock] - </description> - </method> <method name="inst2dict"> <return type="Dictionary"> </return> - <argument index="0" name="inst" type="Object"> + <argument index="0" name="instance" type="Object"> </argument> <description> Returns the passed instance converted to a dictionary (useful for serializing). @@ -459,89 +137,6 @@ [/codeblock] </description> </method> - <method name="instance_from_id"> - <return type="Object"> - </return> - <argument index="0" name="instance_id" type="int"> - </argument> - <description> - Returns the Object that corresponds to [code]instance_id[/code]. All Objects have a unique instance ID. - [codeblock] - var foo = "bar" - func _ready(): - var id = get_instance_id() - var inst = instance_from_id(id) - print(inst.foo) # Prints bar - [/codeblock] - </description> - </method> - <method name="inverse_lerp"> - <return type="float"> - </return> - <argument index="0" name="from" type="float"> - </argument> - <argument index="1" name="to" type="float"> - </argument> - <argument index="2" name="weight" type="float"> - </argument> - <description> - Returns a normalized value considering the given range. This is the opposite of [method lerp]. - [codeblock] - var middle = lerp(20, 30, 0.75) - # `middle` is now 27.5. - # Now, we pretend to have forgotten the original ratio and want to get it back. - var ratio = inverse_lerp(20, 30, 27.5) - # `ratio` is now 0.75. - [/codeblock] - </description> - </method> - <method name="is_equal_approx"> - <return type="bool"> - </return> - <argument index="0" name="a" type="float"> - </argument> - <argument index="1" name="b" type="float"> - </argument> - <description> - Returns [code]true[/code] if [code]a[/code] and [code]b[/code] are approximately equal to each other. - </description> - </method> - <method name="is_inf"> - <return type="bool"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns whether [code]s[/code] is an infinity value (either positive infinity or negative infinity). - </description> - </method> - <method name="is_instance_valid"> - <return type="bool"> - </return> - <argument index="0" name="instance" type="Object"> - </argument> - <description> - Returns whether [code]instance[/code] is a valid object (e.g. has not been deleted from memory). - </description> - </method> - <method name="is_nan"> - <return type="bool"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns whether [code]s[/code] is a NaN (Not-A-Number) value. - </description> - </method> - <method name="is_zero_approx"> - <return type="bool"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns [code]true[/code] if [code]s[/code] is zero or almost zero. - </description> - </method> <method name="len"> <return type="int"> </return> @@ -556,239 +151,20 @@ [/codeblock] </description> </method> - <method name="lerp"> - <return type="Variant"> - </return> - <argument index="0" name="from" type="Variant"> - </argument> - <argument index="1" name="to" type="Variant"> - </argument> - <argument index="2" name="weight" type="float"> - </argument> - <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]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) - [/codeblock] - </description> - </method> - <method name="lerp_angle"> - <return type="float"> - </return> - <argument index="0" name="from" type="float"> - </argument> - <argument index="1" name="to" type="float"> - </argument> - <argument index="2" name="weight" type="float"> - </argument> - <description> - Linearly interpolates between two angles (in radians) by a normalized value. - Similar to [method lerp], but interpolates correctly when the angles wrap around [constant @GDScript.TAU]. - [codeblock] - extends Sprite - var elapsed = 0.0 - func _process(delta): - var min_angle = deg2rad(0.0) - var max_angle = deg2rad(90.0) - rotation = lerp_angle(min_angle, max_angle, elapsed) - elapsed += delta - [/codeblock] - </description> - </method> - <method name="linear2db"> - <return type="float"> - </return> - <argument index="0" name="nrg" type="float"> - </argument> - <description> - Converts from linear energy to decibels (audio). This can be used to implement volume sliders that behave as expected (since volume isn't linear). Example: - [codeblock] - # "Slider" refers to a node that inherits Range such as HSlider or VSlider. - # Its range must be configured to go from 0 to 1. - # Change the bus name if you'd like to change the volume of a specific bus only. - AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), linear2db($Slider.value)) - [/codeblock] - </description> - </method> <method name="load"> <return type="Resource"> </return> <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> - </method> - <method name="log"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Natural logarithm. The amount of time needed to reach a certain level of continuous growth. - [b]Note:[/b] This is not the same as the "log" function on most calculators, which uses a base 10 logarithm. - [codeblock] - log(10) # Returns 2.302585 - [/codeblock] - </description> - </method> - <method name="max"> - <return type="float"> - </return> - <argument index="0" name="a" type="float"> - </argument> - <argument index="1" name="b" type="float"> - </argument> - <description> - Returns the maximum of two values. - [codeblock] - max(1, 2) # Returns 2 - max(-3.99, -4) # Returns -3.99 - [/codeblock] - </description> - </method> - <method name="min"> - <return type="float"> - </return> - <argument index="0" name="a" type="float"> - </argument> - <argument index="1" name="b" type="float"> - </argument> - <description> - Returns the minimum of two values. - [codeblock] - min(1, 2) # Returns 1 - min(-3.99, -4) # Returns -4 - [/codeblock] - </description> - </method> - <method name="move_toward"> - <return type="float"> - </return> - <argument index="0" name="from" type="float"> - </argument> - <argument index="1" name="to" type="float"> - </argument> - <argument index="2" name="delta" type="float"> - </argument> - <description> - 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(10, 5, 4) # Returns 6 - [/codeblock] - </description> - </method> - <method name="nearest_po2"> - <return type="int"> - </return> - <argument index="0" name="value" type="int"> - </argument> - <description> - Returns the nearest larger power of 2 for integer [code]value[/code]. - [codeblock] - nearest_po2(3) # Returns 4 - nearest_po2(4) # Returns 4 - nearest_po2(5) # Returns 8 - [/codeblock] - </description> - </method> - <method name="ord"> - <return type="int"> - </return> - <argument index="0" name="char" type="String"> - </argument> - <description> - Returns an integer representing the Unicode code point of the given Unicode character [code]char[/code]. - [codeblock] - a = ord("A") # a is 65 - a = ord("a") # a is 97 - a = ord("€") # a is 8364 - [/codeblock] - This is the inverse of [method char]. - </description> - </method> - <method name="parse_json"> - <return type="Variant"> - </return> - <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: - [codeblock] - p = parse_json('["a", "b", "c"]') - if typeof(p) == TYPE_ARRAY: - print(p[0]) # Prints a - else: - print("unexpected results") - [/codeblock] - </description> - </method> - <method name="polar2cartesian"> - <return type="Vector2"> - </return> - <argument index="0" name="r" type="float"> - </argument> - <argument index="1" name="th" type="float"> - </argument> - <description> - Converts a 2D point expressed in the polar coordinate system (a distance from the origin [code]r[/code] and an angle [code]th[/code]) to the cartesian coordinate system (X and Y axis). - </description> - </method> - <method name="posmod"> - <return type="int"> - </return> - <argument index="0" name="a" type="int"> - </argument> - <argument index="1" name="b" type="int"> - </argument> - <description> - Returns the integer modulus of [code]a/b[/code] that wraps equally in positive and negative. - [codeblock] - var i = -6 - while i < 5: - prints(i, posmod(i, 3)) - i += 1 - [/codeblock] - Produces: - [codeblock] - -6 0 - -5 1 - -4 2 - -3 0 - -2 1 - -1 2 - 0 0 - 1 1 - 2 2 - 3 0 - 4 1 - [/codeblock] - </description> - </method> - <method name="pow"> - <return type="float"> - </return> - <argument index="0" name="base" type="float"> - </argument> - <argument index="1" name="exp" type="float"> - </argument> - <description> - Returns the result of [code]x[/code] raised to the power of [code]y[/code]. - [codeblock] - pow(2, 5) # Returns 32 - [/codeblock] + This method is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios. </description> </method> <method name="preload"> @@ -797,22 +173,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") - [/codeblock] - </description> - </method> - <method name="print" qualifiers="vararg"> - <return type="void"> - </return> - <description> - Converts one or more arguments to strings in the best way possible and prints them to the console. - [codeblock] - a = [1, 2, 3] - print("a", "b", a) # Prints ab[1, 2, 3] + # Instance a scene. + var diamond = preload("res://diamond.tscn").instance() [/codeblock] </description> </method> @@ -820,7 +185,7 @@ <return type="void"> </return> <description> - Like [method print], but prints only when used in debug mode. + Like [method @GlobalScope.print], but prints only when used in debug mode. </description> </method> <method name="print_stack"> @@ -834,315 +199,21 @@ [/codeblock] </description> </method> - <method name="printerr" qualifiers="vararg"> - <return type="void"> - </return> - <description> - Prints one or more arguments to strings in the best way possible to standard error line. - [codeblock] - printerr("prints to stderr") - [/codeblock] - </description> - </method> - <method name="printraw" qualifiers="vararg"> - <return type="void"> - </return> - <description> - Prints one or more arguments to strings in the best way possible to console. No newline is added at the end. - [codeblock] - printraw("A") - printraw("B") - # Prints AB - [/codeblock] - [b]Note:[/b] Due to limitations with Godot's built-in console, this only prints to the terminal. If you need to print in the editor, use another method, such as [method print]. - </description> - </method> - <method name="prints" qualifiers="vararg"> - <return type="void"> - </return> - <description> - Prints one or more arguments to the console with a space between each argument. - [codeblock] - prints("A", "B", "C") # Prints A B C - [/codeblock] - </description> - </method> - <method name="printt" qualifiers="vararg"> - <return type="void"> - </return> - <description> - Prints one or more arguments to the console with a tab between each argument. - [codeblock] - printt("A", "B", "C") # Prints A B C - [/codeblock] - </description> - </method> - <method name="push_error"> - <return type="void"> - </return> - <argument index="0" name="message" type="String"> - </argument> - <description> - Pushes an error message to Godot's built-in debugger and to the OS terminal. - [codeblock] - push_error("test error") # Prints "test error" to debugger and terminal as error call - [/codeblock] - </description> - </method> - <method name="push_warning"> - <return type="void"> - </return> - <argument index="0" name="message" type="String"> - </argument> - <description> - Pushes a warning message to Godot's built-in debugger and to the OS terminal. - [codeblock] - push_warning("test warning") # Prints "test warning" to debugger and terminal as warning call - [/codeblock] - </description> - </method> - <method name="rad2deg"> - <return type="float"> - </return> - <argument index="0" name="rad" type="float"> - </argument> - <description> - Converts from radians to degrees. - [codeblock] - rad2deg(0.523599) # Returns 30 - [/codeblock] - </description> - </method> - <method name="rand_range"> - <return type="float"> - </return> - <argument index="0" name="from" type="float"> - </argument> - <argument index="1" name="to" type="float"> - </argument> - <description> - Random range, any floating point value between [code]from[/code] and [code]to[/code]. - [codeblock] - prints(rand_range(0, 1), rand_range(0, 1)) # Prints e.g. 0.135591 0.405263 - [/codeblock] - </description> - </method> - <method name="rand_seed"> - <return type="Array"> - </return> - <argument index="0" name="seed" type="int"> - </argument> - <description> - Random from seed: pass a [code]seed[/code], and an array with both number and new seed is returned. "Seed" here refers to the internal state of the pseudo random number generator. The internal state of the current implementation is 64 bits. - </description> - </method> - <method name="randf"> - <return type="float"> - </return> - <description> - Returns a random floating point value on the interval [code][0, 1][/code]. - [codeblock] - randf() # Returns e.g. 0.375671 - [/codeblock] - </description> - </method> - <method name="randi"> - <return type="int"> - </return> - <description> - Returns a random unsigned 32 bit integer. Use remainder to obtain a random value in the interval [code][0, N - 1][/code] (where N is smaller than 2^32). - [codeblock] - randi() # Returns random integer between 0 and 2^32 - 1 - randi() % 20 # Returns random integer between 0 and 19 - randi() % 100 # Returns random integer between 0 and 99 - randi() % 100 + 1 # Returns random integer between 1 and 100 - [/codeblock] - </description> - </method> - <method name="randomize"> - <return type="void"> - </return> - <description> - Randomizes the seed (or the internal state) of the random number generator. Current implementation reseeds using a number based on time. - [codeblock] - func _ready(): - randomize() - [/codeblock] - </description> - </method> <method name="range" qualifiers="vararg"> <return type="Array"> </return> <description> Returns an array with the given range. Range can be 1 argument N (0 to N-1), two arguments (initial, final-1) or three arguments (initial, final-1, increment). [codeblock] - for i in range(4): - print(i) - for i in range(2, 5): - print(i) - for i in range(0, 6, 2): - print(i) + print(range(4)) + print(range(2, 5)) + print(range(0, 6, 2)) [/codeblock] Output: [codeblock] - 0 - 1 - 2 - 3 - - 2 - 3 - 4 - - 0 - 2 - 4 - [/codeblock] - </description> - </method> - <method name="range_lerp"> - <return type="float"> - </return> - <argument index="0" name="value" type="float"> - </argument> - <argument index="1" name="istart" type="float"> - </argument> - <argument index="2" name="istop" type="float"> - </argument> - <argument index="3" name="ostart" type="float"> - </argument> - <argument index="4" name="ostop" type="float"> - </argument> - <description> - Maps a [code]value[/code] from range [code][istart, istop][/code] to [code][ostart, ostop][/code]. - [codeblock] - range_lerp(75, 0, 100, -1, 1) # Returns 0.5 - [/codeblock] - </description> - </method> - <method name="round"> - <return type="float"> - </return> - <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. - [codeblock] - round(2.6) # Returns 3 - [/codeblock] - </description> - </method> - <method name="seed"> - <return type="void"> - </return> - <argument index="0" name="seed" type="int"> - </argument> - <description> - Sets seed for the random number generator. - [codeblock] - my_seed = "Godot Rocks" - seed(my_seed.hash()) - [/codeblock] - </description> - </method> - <method name="sign"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the sign of [code]s[/code]: -1 or 1. Returns 0 if [code]s[/code] is 0. - [codeblock] - sign(-6) # Returns -1 - sign(0) # Returns 0 - sign(6) # Returns 1 - [/codeblock] - </description> - </method> - <method name="sin"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the sine of angle [code]s[/code] in radians. - [codeblock] - sin(0.523599) # Returns 0.5 - [/codeblock] - </description> - </method> - <method name="sinh"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the hyperbolic sine of [code]s[/code]. - [codeblock] - a = log(2.0) # Returns 0.693147 - sinh(a) # Returns 0.75 - [/codeblock] - </description> - </method> - <method name="smoothstep"> - <return type="float"> - </return> - <argument index="0" name="from" type="float"> - </argument> - <argument index="1" name="to" type="float"> - </argument> - <argument index="2" name="weight" 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. - [codeblock] - smoothstep(0, 2, 0.5) # Returns 0.15 - smoothstep(0, 2, 1.0) # Returns 0.5 - smoothstep(0, 2, 2.0) # Returns 1.0 - [/codeblock] - </description> - </method> - <method name="sqrt"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the square root of [code]s[/code]. - [codeblock] - sqrt(9) # Returns 3 - [/codeblock] - </description> - </method> - <method name="step_decimals"> - <return type="int"> - </return> - <argument index="0" name="step" type="float"> - </argument> - <description> - Returns the position of the first non-zero digit, after the decimal point. Note that the maximum return value is 10, which is a design decision in the implementation. - [codeblock] - # n is 0 - n = step_decimals(5) - # n is 4 - n = step_decimals(1.0005) - # n is 9 - n = step_decimals(0.000000005) - [/codeblock] - </description> - </method> - <method name="stepify"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <argument index="1" name="step" type="float"> - </argument> - <description> - Snaps float value [code]s[/code] to a given [code]step[/code]. This can also be used to round a floating point number to an arbitrary number of decimals. - [codeblock] - stepify(100, 32) # Returns 96 - stepify(3.14159, 0.01) # Returns 3.14 + [0, 1, 2, 3] + [2, 3, 4] + [0, 2, 4] [/codeblock] </description> </method> @@ -1159,264 +230,27 @@ [/codeblock] </description> </method> - <method name="str2var"> - <return type="Variant"> - </return> - <argument index="0" name="string" type="String"> - </argument> - <description> - Converts a formatted string that was returned by [method var2str] to the original value. - [codeblock] - a = '{ "a": 1, "b": 2 }' - b = str2var(a) - print(b["a"]) # Prints 1 - [/codeblock] - </description> - </method> - <method name="tan"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the tangent of angle [code]s[/code] in radians. - [codeblock] - tan(deg2rad(45)) # Returns 1 - [/codeblock] - </description> - </method> - <method name="tanh"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Returns the hyperbolic tangent of [code]s[/code]. - [codeblock] - a = log(2.0) # Returns 0.693147 - tanh(a) # Returns 0.6 - [/codeblock] - </description> - </method> - <method name="to_json"> - <return type="String"> - </return> - <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. - [codeblock] - a = { "a": 1, "b": 2 } - b = to_json(a) - print(b) # {"a":1, "b":2} - [/codeblock] - </description> - </method> <method name="type_exists"> <return type="bool"> </return> - <argument index="0" name="type" type="String"> - </argument> - <description> - Returns whether the given class exists in [ClassDB]. - [codeblock] - type_exists("Sprite2D") # Returns true - type_exists("Variant") # Returns false - [/codeblock] - </description> - </method> - <method name="typeof"> - <return type="int"> - </return> - <argument index="0" name="what" type="Variant"> - </argument> - <description> - Returns the internal type of the given Variant object, using the [enum Variant.Type] values. - [codeblock] - p = parse_json('["a", "b", "c"]') - if typeof(p) == TYPE_ARRAY: - print(p[0]) # Prints a - else: - print("unexpected results") - [/codeblock] - </description> - </method> - <method name="validate_json"> - <return type="String"> - </return> - <argument index="0" name="json" type="String"> - </argument> - <description> - Checks that [code]json[/code] is valid JSON data. Returns an empty string if valid, or an error message otherwise. - [codeblock] - j = to_json([1, 2, 3]) - v = validate_json(j) - if not v: - print("valid") - else: - prints("invalid", v) - [/codeblock] - </description> - </method> - <method name="var2bytes"> - <return type="PackedByteArray"> - </return> - <argument index="0" name="var" type="Variant"> - </argument> - <argument index="1" name="full_objects" type="bool" default="false"> + <argument index="0" name="type" type="StringName"> </argument> <description> - Encodes a variable value to a byte array. When [code]full_objects[/code] is [code]true[/code] encoding objects is allowed (and can potentially include code). - </description> - </method> - <method name="var2str"> - <return type="String"> - </return> - <argument index="0" name="var" type="Variant"> - </argument> - <description> - Converts a Variant [code]var[/code] to a formatted string that can later be parsed using [method str2var]. - [codeblock] - a = { "a": 1, "b": 2 } - print(var2str(a)) - [/codeblock] - prints - [codeblock] - { - "a": 1, - "b": 2 - } - [/codeblock] - </description> - </method> - <method name="weakref"> - <return type="WeakRef"> - </return> - <argument index="0" name="obj" type="Object"> - </argument> - <description> - Returns a weak reference to an object. - A weak reference to an object is not enough to keep the object alive: when the only remaining references to a referent are weak references, garbage collection is free to destroy the referent and reuse its memory for something else. However, until the object is actually destroyed the weak reference may return the object even if there are no strong references to it. - </description> - </method> - <method name="wrapf"> - <return type="float"> - </return> - <argument index="0" name="value" type="float"> - </argument> - <argument index="1" name="min" type="float"> - </argument> - <argument index="2" name="max" type="float"> - </argument> - <description> - 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) - [/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] - </description> - </method> - <method name="wrapi"> - <return type="int"> - </return> - <argument index="0" name="value" type="int"> - </argument> - <argument index="1" name="min" type="int"> - </argument> - <argument index="2" name="max" type="int"> - </argument> - <description> - Wraps integer [code]value[/code] between [code]min[/code] and [code]max[/code]. - Usable for creating loop-alike behavior or infinite surfaces. - [codeblock] - # a is 0 - a = wrapi(10, 0, 10) - [/codeblock] - [codeblock] - # a is 9 - a = wrapi(-1, 0, 10) - [/codeblock] - [codeblock] - # Infinite loop between 0 and 9 - frame = wrapi(frame + 1, 0, 10) - [/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. </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/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 62ccb93901..631a102130 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -8,7 +8,7 @@ [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - <link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> + <link title="GDScript tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> </tutorials> <methods> <method name="get_as_byte_code" qualifiers="const"> diff --git a/modules/gdscript/doc_classes/GDScriptFunctionState.xml b/modules/gdscript/doc_classes/GDScriptFunctionState.xml deleted file mode 100644 index 9a73764646..0000000000 --- a/modules/gdscript/doc_classes/GDScriptFunctionState.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="GDScriptFunctionState" inherits="Reference" version="4.0"> - <brief_description> - State of a function call after yielding. - </brief_description> - <description> - 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. - </description> - <tutorials> - </tutorials> - <methods> - <method name="is_valid" qualifiers="const"> - <return type="bool"> - </return> - <argument index="0" name="extended_check" type="bool" default="false"> - </argument> - <description> - Check whether the function call may be resumed. This is not the case if the function state was already resumed. - If [code]extended_check[/code] is enabled, it also checks if the associated script and object still exist. The extended check is done in debug mode as part of [method GDScriptFunctionState.resume], but you can use this if you know you may be trying to resume without knowing for sure the object and/or script have survived up to that point. - </description> - </method> - <method name="resume"> - <return type="Variant"> - </return> - <argument index="0" name="arg" type="Variant" default="null"> - </argument> - <description> - Resume execution of the yielded function call. - If handed an argument, return the argument from the [method @GDScript.yield] call in the yielded function call. You can pass e.g. an [Array] to hand multiple arguments. - This function returns what the resumed function call returns, possibly another function state if yielded again. - </description> - </method> - </methods> - <signals> - <signal name="completed"> - <argument index="0" name="result" type="Variant"> - </argument> - <description> - </description> - </signal> - </signals> - <constants> - </constants> -</class> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 0aca4dbc5e..b792ff54d6 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -29,39 +29,24 @@ /*************************************************************************/ #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) { +static bool _is_char(char32_t 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) { +static bool _is_hex_symbol(char32_t c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } -static bool _is_bin_symbol(CharType c) { +static bool _is_bin_symbol(char32_t c) { return (c == '0' || c == '1'); } -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,146 @@ 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) { + int prev_region_line = p_line - 1; + while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) { + prev_region_line--; + } + for (int i = prev_region_line; i < p_line - 1; i++) { + get_line_syntax_highlighting(i); + } + if (!color_region_cache.has(p_line - 1)) { + get_line_syntax_highlighting(p_line - 1); + } + 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 char32_t *start_key = color_regions[c].start_key.get_data(); + for (int k = 0; k < start_key_length; k++) { + if (start_key[k] != str[from + k]) { + match = false; + 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) { + /* search the line */ + int region_end_index = -1; + int end_key_length = color_regions[in_region].end_key.length(); + const char32_t *end_key = color_regions[in_region].end_key.get_data(); + for (; from < line_length; from++) { + if (line_length - from < end_key_length) { + break; + } + + if (!is_symbol(str[from])) { + continue; + } + + if (str[from] == '\\') { + from++; + continue; + } + + region_end_index = from; + for (int k = 0; k < end_key_length; k++) { + if (end_key[k] != str[from + k]) { + region_end_index = -1; + 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 + (end_key_length - 1); + 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 +231,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 +249,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 +286,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 +298,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 +314,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 +351,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 +367,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 +413,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 == ""); + } - const String text_editor_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); - const bool default_theme = text_editor_color_theme == "Default"; + /* 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_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 +551,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 +567,36 @@ 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"); + } + } + + int at = 0; + for (int i = 0; i < color_regions.size(); i++) { + ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists."); + if (p_start_key.length() < color_regions[i].start_key.length()) { + at++; + } + } + + ColorRegion color_region; + 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.insert(at, color_region); + clear_highlighting_cache(); +} + +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..1b57cb1923 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -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 = false; + }; + 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..6e930b6bf4 --- /dev/null +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -0,0 +1,351 @@ +/*************************************************************************/ +/* gdscript_translation_parser_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_ids, Vector<Vector<String>> *r_ids_ctx_plural) { + // Extract all translatable strings using the parsed tree from GDSriptParser. + // The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e + // Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc. + // Search strings in AssignmentNode -> text = "__", hint_tooltip = "__" etc. + + Error err; + RES loaded_res = ResourceLoader::load(p_path, "", false, &err); + if (err) { + ERR_PRINT("Failed to load " + p_path); + return err; + } + + ids = r_ids; + ids_ctx_plural = r_ids_ctx_plural; + Ref<GDScript> gdscript = loaded_res; + String source_code = gdscript->get_source_code(); + + GDScriptParser parser; + err = parser.parse(source_code, p_path, false); + if (err != OK) { + ERR_PRINT("Failed to parse with GDScript with GDScriptParser."); + return err; + } + + // Traverse through the parsed tree from GDScriptParser. + GDScriptParser::ClassNode *c = parser.get_tree(); + _traverse_class(c); + + return OK; +} + +void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) { + for (int i = 0; i < p_class->members.size(); i++) { + const GDScriptParser::ClassNode::Member &m = p_class->members[i]; + // There are 7 types of Member, but only class, function and variable can contain translatable strings. + switch (m.type) { + case GDScriptParser::ClassNode::Member::CLASS: + _traverse_class(m.m_class); + break; + case GDScriptParser::ClassNode::Member::FUNCTION: + _traverse_function(m.function); + break; + case GDScriptParser::ClassNode::Member::VARIABLE: + _read_variable(m.variable); + break; + default: + break; + } + } +} + +void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) { + _traverse_block(p_func->body); +} + +void GDScriptEditorTranslationParserPlugin::_read_variable(const GDScriptParser::VariableNode *p_var) { + _assess_expression(p_var->initializer); +} + +void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser::SuiteNode *p_suite) { + if (!p_suite) { + return; + } + + const Vector<GDScriptParser::Node *> &statements = p_suite->statements; + for (int i = 0; i < statements.size(); i++) { + GDScriptParser::Node *statement = statements[i]; + + // Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings. + switch (statement->type) { + case GDScriptParser::Node::VARIABLE: + _assess_expression(static_cast<GDScriptParser::VariableNode *>(statement)->initializer); + break; + case GDScriptParser::Node::IF: { + GDScriptParser::IfNode *if_node = static_cast<GDScriptParser::IfNode *>(statement); + _assess_expression(if_node->condition); + //FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if(). + _traverse_block(if_node->true_block); + _traverse_block(if_node->false_block); + break; + } + case GDScriptParser::Node::FOR: { + GDScriptParser::ForNode *for_node = static_cast<GDScriptParser::ForNode *>(statement); + _assess_expression(for_node->list); + _traverse_block(for_node->loop); + break; + } + case GDScriptParser::Node::WHILE: { + GDScriptParser::WhileNode *while_node = static_cast<GDScriptParser::WhileNode *>(statement); + _assess_expression(while_node->condition); + _traverse_block(while_node->loop); + break; + } + case GDScriptParser::Node::MATCH: { + GDScriptParser::MatchNode *match_node = static_cast<GDScriptParser::MatchNode *>(statement); + _assess_expression(match_node->test); + for (int j = 0; j < match_node->branches.size(); j++) { + _traverse_block(match_node->branches[j]->block); + } + break; + } + case GDScriptParser::Node::RETURN: + _assess_expression(static_cast<GDScriptParser::ReturnNode *>(statement)->return_value); + break; + case GDScriptParser::Node::ASSERT: + _assess_expression((static_cast<GDScriptParser::AssertNode *>(statement))->condition); + break; + case GDScriptParser::Node::ASSIGNMENT: + _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(statement)); + break; + default: + if (statement->is_expression()) { + _assess_expression(static_cast<GDScriptParser::ExpressionNode *>(statement)); + } + break; + } + } +} + +void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::ExpressionNode *p_expression) { + // Explore all ExpressionNodes to find CallNodes which contain translation strings, such as tr(), set_text() etc. + // tr() can be embedded quite deep within multiple ExpressionNodes so need to dig down to search through all ExpressionNodes. + if (!p_expression) { + return; + } + + // ExpressionNode of type await, cast, get_node, identifier, literal, preload, self, subscript, unary are ignored as they can't be CallNode + // containing translation strings. + switch (p_expression->type) { + case GDScriptParser::Node::ARRAY: { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(p_expression); + for (int i = 0; i < array_node->elements.size(); i++) { + _assess_expression(array_node->elements[i]); + } + break; + } + case GDScriptParser::Node::ASSIGNMENT: + _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression)); + break; + case GDScriptParser::Node::BINARY_OPERATOR: { + GDScriptParser::BinaryOpNode *binary_op_node = static_cast<GDScriptParser::BinaryOpNode *>(p_expression); + _assess_expression(binary_op_node->left_operand); + _assess_expression(binary_op_node->right_operand); + break; + } + case GDScriptParser::Node::CALL: { + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_expression); + _extract_from_call(call_node); + for (int i = 0; i < call_node->arguments.size(); i++) { + _assess_expression(call_node->arguments[i]); + } + } break; + case GDScriptParser::Node::DICTIONARY: { + GDScriptParser::DictionaryNode *dict_node = static_cast<GDScriptParser::DictionaryNode *>(p_expression); + for (int i = 0; i < dict_node->elements.size(); i++) { + _assess_expression(dict_node->elements[i].key); + _assess_expression(dict_node->elements[i].value); + } + break; + } + case GDScriptParser::Node::TERNARY_OPERATOR: { + GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<GDScriptParser::TernaryOpNode *>(p_expression); + _assess_expression(ternary_op_node->condition); + _assess_expression(ternary_op_node->true_expr); + _assess_expression(ternary_op_node->false_expr); + break; + } + default: + break; + } +} + +void GDScriptEditorTranslationParserPlugin::_assess_assignment(GDScriptParser::AssignmentNode *p_assignment) { + // Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____" + + StringName assignee_name; + if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + assignee_name = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name; + } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { + assignee_name = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name; + } + + if (assignment_patterns.has(assignee_name) && p_assignment->assigned_value->type == GDScriptParser::Node::LITERAL) { + // If the assignment is towards one of the extract patterns (text, hint_tooltip etc.), and the value is a string literal, we collect the string. + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_assignment->assigned_value)->value); + } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) { + // FileDialog.filters accepts assignment in the form of PackedStringArray. For example, + // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). + + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value); + if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]); + + // Extract the name in "extension ; name" of PackedStringArray. + for (int i = 0; i < array_node->elements.size(); i++) { + _extract_fd_literals(array_node->elements[i]); + } + } + } else { + // If the assignee is not in extract patterns or the assigned_value is not Literal type, try to see if the assigned_value contains tr(). + _assess_expression(p_assignment->assigned_value); + } +} + +void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::CallNode *p_call) { + // Extract the translatable strings coming from function calls. For example: + // tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____"). + + StringName function_name = p_call->function_name; + + // Variables for extracting tr() and tr_n(). + Vector<String> id_ctx_plural; + id_ctx_plural.resize(3); + bool extract_id_ctx_plural = true; + + if (function_name == tr_func) { + // Extract from tr(id, ctx). + for (int i = 0; i < p_call->arguments.size(); i++) { + if (p_call->arguments[i]->type == GDScriptParser::Node::LITERAL) { + id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[i])->value; + } else { + // Avoid adding something like tr("Flying dragon", var_context_level_1). We want to extract both id and context together. + extract_id_ctx_plural = false; + } + } + if (extract_id_ctx_plural) { + ids_ctx_plural->push_back(id_ctx_plural); + } + } else if (function_name == trn_func) { + // Extract from tr_n(id, plural, n, ctx). + Vector<int> indices; + indices.push_back(0); + indices.push_back(3); + indices.push_back(1); + for (int i = 0; i < indices.size(); i++) { + if (indices[i] >= p_call->arguments.size()) { + continue; + } + + if (p_call->arguments[indices[i]]->type == GDScriptParser::Node::LITERAL) { + id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[indices[i]])->value; + } else { + extract_id_ctx_plural = false; + } + } + if (extract_id_ctx_plural) { + ids_ctx_plural->push_back(id_ctx_plural); + } + } else if (first_arg_patterns.has(function_name)) { + // Extracting argument with only string literals. In other words, not extracting something like set_text("hello " + some_var). + if (p_call->arguments[0]->type == GDScriptParser::Node::LITERAL) { + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[0])->value); + } + } else if (second_arg_patterns.has(function_name)) { + if (p_call->arguments[1]->type == GDScriptParser::Node::LITERAL) { + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[1])->value); + } + } else if (function_name == fd_add_filter) { + // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images"). + _extract_fd_literals(p_call->arguments[0]); + + } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) { + // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example, + // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])). + + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_call->arguments[0]); + if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]); + for (int i = 0; i < array_node->elements.size(); i++) { + _extract_fd_literals(array_node->elements[i]); + } + } + } +} + +void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) { + // Extract the name in "extension ; name". + + if (p_expression->type == GDScriptParser::Node::LITERAL) { + String arg_val = String(static_cast<GDScriptParser::LiteralNode *>(p_expression)->value); + PackedStringArray arr = arg_val.split(";", true); + if (arr.size() != 2) { + ERR_PRINT("Argument for setting FileDialog has bad format."); + return; + } + ids->push_back(arr[1].strip_edges()); + } +} + +GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() { + assignment_patterns.insert("text"); + assignment_patterns.insert("placeholder_text"); + assignment_patterns.insert("hint_tooltip"); + + first_arg_patterns.insert("set_text"); + first_arg_patterns.insert("set_tooltip"); + first_arg_patterns.insert("set_placeholder"); + first_arg_patterns.insert("add_tab"); + first_arg_patterns.insert("add_check_item"); + first_arg_patterns.insert("add_item"); + first_arg_patterns.insert("add_multistate_item"); + first_arg_patterns.insert("add_radio_check_item"); + first_arg_patterns.insert("add_separator"); + first_arg_patterns.insert("add_submenu_item"); + + second_arg_patterns.insert("set_tab_title"); + second_arg_patterns.insert("add_icon_check_item"); + second_arg_patterns.insert("add_icon_item"); + second_arg_patterns.insert("add_icon_radio_check_item"); + second_arg_patterns.insert("set_item_text"); +} diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h new file mode 100644 index 0000000000..5358a77140 --- /dev/null +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* gdscript_translation_parser_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "core/templates/set.h" +#include "editor/editor_translation_parser.h" +#include "modules/gdscript/gdscript_parser.h" +#include "modules/regex/regex.h" + +class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { + GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); + + Vector<String> *ids; + Vector<Vector<String>> *ids_ctx_plural; + + // List of patterns used for extracting translation strings. + StringName tr_func = "tr"; + StringName trn_func = "tr_n"; + Set<StringName> assignment_patterns; + Set<StringName> first_arg_patterns; + Set<StringName> second_arg_patterns; + // FileDialog patterns. + StringName fd_add_filter = "add_filter"; + StringName fd_set_filter = "set_filters"; + StringName fd_filters = "filters"; + + void _traverse_class(const GDScriptParser::ClassNode *p_class); + void _traverse_function(const GDScriptParser::FunctionNode *p_func); + void _traverse_block(const GDScriptParser::SuiteNode *p_suite); + + void _read_variable(const GDScriptParser::VariableNode *p_var); + void _assess_expression(GDScriptParser::ExpressionNode *p_expression); + void _assess_assignment(GDScriptParser::AssignmentNode *p_assignment); + void _extract_from_call(GDScriptParser::CallNode *p_call); + void _extract_fd_literals(GDScriptParser::ExpressionNode *p_expression); + +public: + virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) 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 06ab9e226d..502e294275 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -32,24 +32,26 @@ #include <stdint.h> +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/core_constants.h" #include "core/core_string_names.h" -#include "core/engine.h" -#include "core/global_constants.h" #include "core/io/file_access_encrypted.h" #include "core/os/file_access.h" #include "core/os/os.h" -#include "core/project_settings.h" +#include "gdscript_analyzer.h" +#include "gdscript_cache.h" #include "gdscript_compiler.h" +#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); @@ -107,10 +116,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco MutexLock lock(GDScriptLanguage::singleton->lock); instances.insert(instance->owner); } - if (p_argcount < 0) { - return instance; - } - initializer->call(instance, p_args, p_argcount, r_error); + + _super_implicit_constructor(this, instance, r_error); if (r_error.error != Callable::CallError::CALL_OK) { instance->script = Ref<GDScript>(); instance->owner->set_script_instance(nullptr); @@ -120,12 +127,27 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco } ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance."); } + + if (p_argcount < 0) { + return instance; + } + if (initializer != nullptr) { + initializer->call(instance, p_args, p_argcount, r_error); + if (r_error.error != Callable::CallError::CALL_OK) { + instance->script = Ref<GDScript>(); + instance->owner->set_script_instance(nullptr); + { + MutexLock lock(GDScriptLanguage::singleton->lock); + instances.erase(p_owner); + } + ERR_FAIL_V_MSG(nullptr, "Error constructing a GDScriptInstance."); + } + } //@TODO make thread safe return instance; } Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - /* STEP 1, CREATE */ if (!valid) { @@ -171,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 @@ -180,7 +201,6 @@ bool GDScript::can_instance() const { } Ref<Script> GDScript::get_base_script() const { - if (_base) { return Ref<GDScript>(_base); } else { @@ -189,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; } @@ -207,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 { - +void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { const GDScript *current = this; while (current) { for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) { @@ -221,27 +239,35 @@ void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { MethodInfo mi; mi.name = E->key(); for (int i = 0; i < func->get_argument_count(); i++) { - mi.arguments.push_back(func->get_argument_type(i)); + PropertyInfo arginfo = func->get_argument_type(i); +#ifdef TOOLS_ENABLED + arginfo.name = func->get_argument_name(i); +#endif + mi.arguments.push_back(arginfo); } mi.return_val = func->get_return_type(); - p_list->push_back(mi); + r_list->push_back(mi); + } + if (!p_include_base) { + return; } current = current->_base; } } -void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { +void GDScript::get_script_method_list(List<MethodInfo> *r_list) const { + _get_script_method_list(r_list, true); +} +void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const { const GDScript *sptr = this; List<PropertyInfo> props; 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; @@ -252,28 +278,33 @@ 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]); } + if (!p_include_base) { + break; + } sptr = sptr->_base; } for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - p_list->push_back(E->get()); + r_list->push_back(E->get()); } } -bool GDScript::has_method(const StringName &p_method) const { +void GDScript::get_script_property_list(List<PropertyInfo> *r_list) const { + _get_script_property_list(r_list, true); +} +bool GDScript::has_method(const StringName &p_method) const { return member_functions.has(p_method); } 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; @@ -287,7 +318,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); @@ -304,14 +334,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() + "'"); } @@ -335,24 +364,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; @@ -361,7 +389,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); } @@ -374,12 +401,194 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro propnames.push_back(E->get()); } } -#endif -bool GDScript::_update_exports() { +void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) { + if (_owner) { + _owner->_add_doc(p_inner_class); + } else { + for (int i = 0; i < docs.size(); i++) { + if (docs[i].name == p_inner_class.name) { + docs.remove(i); + break; + } + } + docs.append(p_inner_class); + } +} + +void GDScript::_clear_doc() { + docs.clear(); + doc = DocData::ClassDoc(); +} + +void GDScript::_update_doc() { + _clear_doc(); + + doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; + if (!name.is_empty()) { + doc.name = name; + } else { + doc.name = doc.script_path; + } + + if (_owner) { + doc.name = _owner->doc.name + "." + doc.name; + doc.script_path = doc.script_path + "." + doc.name; + } + + doc.is_script_doc = true; + + if (base.is_valid() && base->is_valid()) { + if (base->doc.name != String()) { + doc.inherits = base->doc.name; + } else { + doc.inherits = base->get_instance_base_type(); + } + } else if (native.is_valid()) { + doc.inherits = native->get_name(); + } + + doc.brief_description = doc_brief_description; + doc.description = doc_description; + doc.tutorials = doc_tutorials; + + for (Map<String, DocData::EnumDoc>::Element *E = doc_enums.front(); E; E = E->next()) { + if (E->value().description != "") { + doc.enums[E->key()] = E->value().description; + } + } + + List<MethodInfo> methods; + _get_script_method_list(&methods, false); + for (int i = 0; i < methods.size(); i++) { + // Ignore internal methods. + if (methods[i].name[0] == '@') { + continue; + } + + DocData::MethodDoc method_doc; + const String &class_name = methods[i].name; + if (member_functions.has(class_name)) { + GDScriptFunction *fn = member_functions[class_name]; + + // Change class name if return type is script reference. + GDScriptDataType return_type = fn->get_return_type(); + if (return_type.kind == GDScriptDataType::GDSCRIPT) { + methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type)); + } + + // Change class name if argumetn is script reference. + for (int j = 0; j < fn->get_argument_count(); j++) { + GDScriptDataType arg_type = fn->get_argument_type(j); + if (arg_type.kind == GDScriptDataType::GDSCRIPT) { + methods[i].arguments[j].class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(arg_type.script_type)); + } + } + } + if (doc_functions.has(methods[i].name)) { + DocData::method_doc_from_methodinfo(method_doc, methods[i], doc_functions[methods[i].name]); + } else { + DocData::method_doc_from_methodinfo(method_doc, methods[i], String()); + } + doc.methods.push_back(method_doc); + } + + List<PropertyInfo> props; + _get_script_property_list(&props, false); + for (int i = 0; i < props.size(); i++) { + ScriptMemberInfo scr_member_info; + scr_member_info.propinfo = props[i]; + scr_member_info.propinfo.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + if (member_indices.has(props[i].name)) { + const MemberInfo &mi = member_indices[props[i].name]; + scr_member_info.setter = mi.setter; + scr_member_info.getter = mi.getter; + if (mi.data_type.kind == GDScriptDataType::GDSCRIPT) { + scr_member_info.propinfo.class_name = _get_gdscript_reference_class_name( + Object::cast_to<GDScript>(mi.data_type.script_type)); + } + } + if (member_default_values.has(props[i].name)) { + scr_member_info.has_default_value = true; + scr_member_info.default_value = member_default_values[props[i].name]; + } + if (doc_variables.has(props[i].name)) { + scr_member_info.doc_string = doc_variables[props[i].name]; + } + + DocData::PropertyDoc prop_doc; + DocData::property_doc_from_scriptmemberinfo(prop_doc, scr_member_info); + doc.properties.push_back(prop_doc); + } + + List<MethodInfo> signals; + _get_script_signal_list(&signals, false); + for (int i = 0; i < signals.size(); i++) { + DocData::MethodDoc signal_doc; + if (doc_signals.has(signals[i].name)) { + DocData::signal_doc_from_methodinfo(signal_doc, signals[i], signals[i].name); + } else { + DocData::signal_doc_from_methodinfo(signal_doc, signals[i], String()); + } + doc.signals.push_back(signal_doc); + } + + for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { + if (subclasses.has(E->key())) { + continue; + } + + // Enums. + bool is_enum = false; + if (E->value().get_type() == Variant::DICTIONARY) { + if (doc_enums.has(E->key())) { + is_enum = true; + for (int i = 0; i < doc_enums[E->key()].values.size(); i++) { + doc_enums[E->key()].values.write[i].enumeration = E->key(); + doc.constants.push_back(doc_enums[E->key()].values[i]); + } + } + } + if (!is_enum && doc_enums.has("@unnamed_enums")) { + for (int i = 0; i < doc_enums["@unnamed_enums"].values.size(); i++) { + if (E->key() == doc_enums["@unnamed_enums"].values[i].name) { + is_enum = true; + DocData::ConstantDoc constant_doc; + constant_doc.enumeration = "@unnamed_enums"; + DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_enums["@unnamed_enums"].values[i].description); + doc.constants.push_back(constant_doc); + break; + } + } + } + if (!is_enum) { + DocData::ConstantDoc constant_doc; + String doc_description; + if (doc_constants.has(E->key())) { + doc_description = doc_constants[E->key()]; + } + DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_description); + doc.constants.push_back(constant_doc); + } + } + + for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { + E->get()->_update_doc(); + } + + _add_doc(doc); +} +#endif +bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { #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) { @@ -388,21 +597,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()); @@ -411,32 +619,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()); } @@ -448,19 +653,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; @@ -473,7 +695,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; } } @@ -498,18 +737,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(); } @@ -517,16 +760,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); @@ -538,40 +778,75 @@ 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 return OK; } + { + String source_path = path; + if (source_path.is_empty()) { + source_path = get_path(); + } + if (!source_path.is_empty()) { + MutexLock lock(GDScriptCache::singleton->lock); + if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; + } + } + } + valid = false; GDScriptParser parser; - Error err = parser.parse(source, basedir, false, path); + Error err = parser.parse(source, path, false); + if (err) { + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); + } + // TODO: Show all error messages. + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + ERR_FAIL_V(ERR_PARSE_ERROR); + } + + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + if (err) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_error_line(), "Parser Error: " + parser.get_error()); + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); + } + + const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front(); + while (e != nullptr) { + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + e = e->next(); } - _err_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); ERR_FAIL_V(ERR_PARSE_ERROR); } - bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool_script(); + bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool(); GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); - if (err) { +#ifdef TOOLS_ENABLED + _update_doc(); +#endif + 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()); } - _err_print_error("GDScript::reload", 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_print_error("GDScript::reload", path.is_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); } else { return err; @@ -582,7 +857,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 @@ -590,7 +865,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); } @@ -600,12 +874,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(); @@ -635,12 +907,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 { - if (p_rpc_method_id >= rpc_functions.size()) return 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 { - if (p_rpc_method_id >= rpc_functions.size()) return 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; } @@ -662,12 +938,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 { - if (p_rset_member_id >= rpc_variables.size()) return 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 { - if (p_rset_member_id >= rpc_variables.size()) return 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; } @@ -676,13 +956,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); @@ -696,16 +973,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; } @@ -714,7 +987,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; } @@ -723,7 +995,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; } @@ -731,120 +1002,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); } @@ -859,7 +1052,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."); } @@ -872,23 +1064,20 @@ 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; } @@ -911,8 +1100,9 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const { } 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); } @@ -923,10 +1113,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_list, bool p_include_base) const { for (const Map<StringName, Vector<StringName>>::Element *E = _signals.front(); E; E = E->next()) { - MethodInfo mi; mi.name = E->key(); for (int i = 0; i < E->get().size(); i++) { @@ -934,23 +1123,45 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { arg.name = E->get()[i]; mi.arguments.push_back(arg); } - r_signals->push_back(mi); + r_list->push_back(mi); + } + + if (!p_include_base) { + return; } if (base.is_valid()) { - base->get_script_signal_list(r_signals); + base->get_script_signal_list(r_list); } #ifdef TOOLS_ENABLED else if (base_cache.is_valid()) { - base_cache->get_script_signal_list(r_signals); + base_cache->get_script_signal_list(r_list); } #endif } +void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + _get_script_signal_list(r_signals, true); +} + +String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) { + ERR_FAIL_NULL_V(p_gdscript, String()); + + String class_name; + while (p_gdscript) { + if (class_name == "") { + class_name = p_gdscript->get_script_class_name(); + } else { + class_name = p_gdscript->get_script_class_name() + "." + class_name; + } + p_gdscript = p_gdscript->_owner; + } + return class_name; +} + GDScript::GDScript() : script_list(this) { - valid = false; subclass_count = 0; initializer = nullptr; @@ -995,8 +1206,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); } @@ -1037,13 +1249,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. @@ -1052,12 +1266,34 @@ void GDScript::_init_rpc_methods_properties() { } GDScript::~GDScript() { + { + MutexLock lock(GDScriptLanguage::get_singleton()->lock); + + while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. + pending_func_states.remove(E); + E->self()->_clear_stack(); + } + } + for (Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) { memdelete(E->get()); } + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. + GDScriptCache::remove_script(get_path()); + } + _save_orphaned_subclasses(); +#ifdef TOOLS_ENABLED + // Clearing inner class doc, script doc only cleared when the script source deleted. + if (_owner) { + _clear_doc(); + } +#endif + #ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->lock); @@ -1072,7 +1308,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); @@ -1090,7 +1325,8 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { // Try conversion Callable::CallError ce; const Variant *value = &p_value; - Variant converted = Variant::construct(member->data_type.builtin_type, &value, 1, ce); + Variant converted; + Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce); if (ce.error == Callable::CallError::CALL_OK) { members.write[member->index] = converted; return true; @@ -1107,17 +1343,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; } @@ -1126,10 +1361,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) { @@ -1146,7 +1379,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); @@ -1159,9 +1391,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 }; @@ -1180,20 +1437,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; } @@ -1204,19 +1461,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")); @@ -1225,12 +1478,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); } @@ -1241,7 +1497,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; @@ -1252,7 +1507,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]); } @@ -1260,23 +1514,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; @@ -1284,19 +1535,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); @@ -1309,42 +1560,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 }; @@ -1369,27 +1585,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(); } @@ -1434,7 +1651,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 @@ -1444,7 +1660,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; @@ -1457,7 +1672,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; } @@ -1470,9 +1684,16 @@ 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()) { + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. + pending_func_states.remove(E); + E->self()->_clear_stack(); + } + + if (script.is_valid() && owner) { script->instances.erase(owner); } } @@ -1482,14 +1703,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; @@ -1501,7 +1720,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); } @@ -1515,12 +1733,10 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { } void GDScriptLanguage::init() { - //populate global constants - int gcc = GlobalConstants::get_global_constant_count(); + int gcc = CoreConstants::get_global_constant_count(); for (int i = 0; i < gcc; i++) { - - _add_global(StaticCString::create(GlobalConstants::get_global_constant_name(i)), GlobalConstants::get_global_constant_value(i)); + _add_global(StaticCString::create(CoreConstants::get_global_constant_name(i)), CoreConstants::get_global_constant_value(i)); } _add_global(StaticCString::create("PI"), Math_PI); @@ -1533,14 +1749,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); } @@ -1550,29 +1767,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); @@ -1595,7 +1810,6 @@ void GDScriptLanguage::profiling_start() { } void GDScriptLanguage::profiling_stop() { - #ifdef DEBUG_ENABLED MutexLock lock(this->lock); @@ -1604,7 +1818,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 @@ -1612,8 +1825,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; @@ -1627,7 +1841,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 @@ -1635,8 +1848,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; @@ -1652,12 +1866,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()) { @@ -1673,7 +1886,6 @@ struct GDScriptDepSort { }; void GDScriptLanguage::reload_all_scripts() { - #ifdef DEBUG_ENABLED print_verbose("GDScript: Reloading all scripts"); List<Ref<GDScript>> scripts; @@ -1695,7 +1907,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); @@ -1704,7 +1915,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; @@ -1714,7 +1924,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(); @@ -1730,16 +1939,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()]; @@ -1748,7 +1956,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()); @@ -1763,7 +1970,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); @@ -1783,18 +1989,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) @@ -1833,7 +2038,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so } void GDScriptLanguage::frame() { - calls = 0; #ifdef DEBUG_ENABLED @@ -1857,7 +2061,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", @@ -1880,6 +2084,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { // functions "as", "assert", + "await", "breakpoint", "class", "class_name", @@ -1887,15 +2092,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 @@ -1909,35 +2112,29 @@ 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++; } - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - p_words->push_back(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))); + List<StringName> functions; + GDScriptUtilityFunctions::get_function_list(&functions); + + for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { + p_words->push_back(String(E->get())); } } 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); @@ -1948,60 +2145,61 @@ 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.is_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.is_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; } String subsource = subfile->get_as_utf8_string(); - if (subsource.empty()) { + if (subsource.is_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; @@ -2014,8 +2212,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; @@ -2026,183 +2224,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; @@ -2240,18 +2268,43 @@ 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); } - singleton = nullptr; + + // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). + SelfList<GDScript> *s = script_list.first(); + while (s) { + GDScript *script = s->self(); + // This ensures the current script is not released before we can check what's the next one + // in the list (we can't get the next upfront because we don't know if the reference breaking + // will cause it -or any other after it, for that matter- to be released so the next one + // is not the same as before). + script->reference(); + + for (Map<StringName, GDScriptFunction *>::Element *E = script->member_functions.front(); E; E = E->next()) { + GDScriptFunction *func = E->get(); + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { + E->get().data_type.script_type_ref = Ref<Script>(); + } + + s = s->next(); + script->unreference(); + } + + singleton = NULL; } void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass) { @@ -2260,81 +2313,73 @@ 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, bool p_no_cache) { - - if (r_error) + 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 + "'."); String source = file->get_as_utf8_string(); - if (source.empty()) { + if (source.is_empty()) { return; } 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; } @@ -2344,7 +2389,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); @@ -2371,12 +2415,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 5fdc25669f..37f01b2571 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -33,13 +33,13 @@ #include "core/debugger/engine_debugger.h" #include "core/debugger/script_debugger.h" +#include "core/doc_data.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" -#include "core/script_language.h" +#include "core/object/script_language.h" #include "gdscript_function.h" class GDScriptNativeClass : public Reference { - GDCLASS(GDScriptNativeClass, Reference); StringName name; @@ -56,7 +56,6 @@ public: }; class GDScript : public Script { - GDCLASS(GDScript, Script); bool tool; bool valid; @@ -71,9 +70,10 @@ class GDScript : public Script { friend class GDScriptInstance; friend class GDScriptFunction; + friend class GDScriptAnalyzer; friend class GDScriptCompiler; - friend class GDScriptFunctions; friend class GDScriptLanguage; + friend struct GDScriptUtilityFunctionsDefinitions; Ref<GDScriptNativeClass> native; Ref<GDScript> base; @@ -92,9 +92,7 @@ class GDScript : public Script { #ifdef TOOLS_ENABLED Map<StringName, int> member_lines; - Map<StringName, Variant> member_default_values; - List<PropertyInfo> members_cache; Map<StringName, Variant> member_default_values_cache; Ref<GDScript> base_cache; @@ -103,10 +101,25 @@ class GDScript : public Script { bool placeholder_fallback_enabled; void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames); + DocData::ClassDoc doc; + Vector<DocData::ClassDoc> docs; + String doc_brief_description; + String doc_description; + Vector<DocData::TutorialDoc> doc_tutorials; + Map<String, String> doc_functions; + Map<String, String> doc_variables; + Map<String, String> doc_constants; + Map<String, String> doc_signals; + Map<String, DocData::EnumDoc> doc_enums; + void _clear_doc(); + void _update_doc(); + void _add_doc(const DocData::ClassDoc &p_inner_class); + #endif Map<StringName, PropertyInfo> member_info; - 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 +130,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 +140,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,25 +149,31 @@ 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(); + void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const; + void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const; + void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const; + + // This method will map the class name from "Reference" to "MyClass.InnerClass". + static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); 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; + 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; } @@ -164,10 +186,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; } @@ -175,21 +197,27 @@ 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 override; - virtual Ref<Script> get_base_script() 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 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 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 bool has_source_code() const; - virtual String get_source_code() const; - virtual void set_source_code(const String &p_code); - virtual void update_exports(); +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const override { + return docs; + } +#endif // TOOLS_ENABLED - virtual Error reload(bool p_keep_state = false); + 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); @@ -197,42 +225,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(); @@ -242,8 +270,8 @@ public: class GDScriptInstance : public ScriptInstance { friend class GDScript; friend class GDScriptFunction; - friend class GDScriptFunctions; friend class GDScriptCompiler; + friend struct GDScriptUtilityFunctionsDefinitions; ObjectID owner_id; Object *owner; @@ -254,7 +282,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; } @@ -267,8 +295,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]; } @@ -299,53 +325,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; @@ -355,7 +336,6 @@ class GDScriptLanguage : public ScriptLanguage { Map<StringName, Variant> named_globals; struct CallLevel { - Variant *stack; GDScriptFunction *function; GDScriptInstance *instance; @@ -394,12 +374,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 @@ -417,15 +398,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; @@ -435,8 +416,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); @@ -451,7 +433,6 @@ public: } struct { - StringName _init; StringName _notification; StringName _set; @@ -489,7 +470,8 @@ public: virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; - virtual bool can_inherit_from_file() { return true; } + virtual bool supports_documentation() const; + virtual bool can_inherit_from_file() const { return true; } virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const; virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp new file mode 100644 index 0000000000..a6138cc564 --- /dev/null +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -0,0 +1,3446 @@ +/*************************************************************************/ +/* gdscript_analyzer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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/config/project_settings.h" +#include "core/io/resource_loader.h" +#include "core/object/class_db.h" +#include "core/object/script_language.h" +#include "core/os/file_access.h" +#include "core/templates/hash_map.h" +#include "gdscript.h" +#include "gdscript_utility_functions.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.is_empty()) { + const char **class_name = underscore_classes; + while (*class_name != nullptr) { + underscore_map[*class_name] = String("_") + *class_name; + class_name++; + } + } + if (underscore_map.has(p_source)) { + return underscore_map[p_source]; + } + return p_source; +} + +static MethodInfo info_from_utility_func(const StringName &p_function) { + ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); + + MethodInfo info(p_function); + + if (Variant::has_utility_function_return_value(p_function)) { + info.return_val.type = Variant::get_utility_function_return_type(p_function); + if (info.return_val.type == Variant::NIL) { + info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + } + + if (Variant::is_utility_function_vararg(p_function)) { + info.flags |= METHOD_FLAG_VARARG; + } else { + for (int i = 0; i < Variant::get_utility_function_argument_count(p_function); i++) { + PropertyInfo pi; +#ifdef DEBUG_METHODS_ENABLED + pi.name = Variant::get_utility_function_argument_name(p_function, i); +#else + pi.name = "arg" + itos(i + 1); +#endif + pi.type = Variant::get_utility_function_argument_type(p_function, i); + if (pi.type == Variant::NIL) { + pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + info.arguments.push_back(pi); + } + } + + return info; +} + +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.is_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::INTERFACE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); + return err; + } + + base = parser->get_parser()->head->get_datatype(); + } else { + if (p_class->extends.is_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::INTERFACE_SOLVED); + if (err != OK) { + push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); + return err; + } + base = parser->get_parser()->head->get_datatype(); + } + } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { + const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); + 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::INTERFACE_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; + } + + // Check for cyclic inheritance. + const GDScriptParser::ClassNode *base_class = result.class_type; + while (base_class) { + if (base_class->fqcn == p_class->fqcn) { + push_error("Cyclic inheritance.", p_class); + return ERR_PARSE_ERROR; + } + base_class = base_class->base_type.class_type; + } + + p_class->base_type = result; + class_type.native_type = result.native_type; + p_class->set_datatype(class_type); + + if (p_recursive) { + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { + Error err = resolve_inheritance(p_class->members[i].m_class, true); + if (err) { + return err; + } + } + } + } + + 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.is_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 (datatype.type_source != GDScriptParser::DataType::UNDETECTED) { + datatype.type_source = GDScriptParser::DataType::INFERRED; + } + } + + 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)) { + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(member.variable->initializer); + } + } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { +#ifdef DEBUG_ENABLED + parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#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.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } + + 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 = PROPERTY_HINT_RESOURCE_TYPE; + member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); + } else { + push_error(R"(Export type can only be built-in or a resource.)", member.variable); + } + 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->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer)); + } else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer)); + } + + if (!member.constant->initializer->is_constant) { + push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); + } + + 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; + + // Enums can't be nested, so we can safely override this. + current_enum = member.m_enum; + + for (int j = 0; j < member.m_enum->values.size(); j++) { + GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j]; + + if (element.custom_value) { + reduce_expression(element.custom_value); + if (!element.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", element.custom_value); + } else if (element.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", element.custom_value); + } else { + element.value = element.custom_value->reduced_value; + element.resolved = true; + } + } else { + if (element.index > 0) { + element.value = element.parent_enum->values[element.index - 1].value + 1; + } else { + element.value = 0; + } + element.resolved = true; + } + + enum_type.enum_values[element.identifier->name] = element.value; + } + + current_enum = nullptr; + + member.m_enum->set_datatype(enum_type); + } break; + case GDScriptParser::ClassNode::Member::FUNCTION: + resolve_function_signature(member.function); + break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + if (member.enum_value.custom_value) { + current_enum = member.enum_value.parent_enum; + reduce_expression(member.enum_value.custom_value); + current_enum = nullptr; + + if (!member.enum_value.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", member.enum_value.custom_value); + } else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", member.enum_value.custom_value); + } else { + member.enum_value.value = member.enum_value.custom_value->reduced_value; + member.enum_value.resolved = true; + } + } else { + if (member.enum_value.index > 0) { + member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; + } else { + member.enum_value.value = 0; + } + member.enum_value.resolved = true; + } + // Also update the original references. + member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value; + p_class->members.write[i].enum_value = member.enum_value; + } break; + case GDScriptParser::ClassNode::Member::CLASS: + break; // Done later. + case GDScriptParser::ClassNode::Member::UNDEFINED: + 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_parameter(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_parameter(p_function->parameters[i]); +#ifdef DEBUG_ENABLED + if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { + parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); + } + is_shadowing(p_function->parameters[i]->identifier, "function parameter"); +#endif // DEBUG_ENABLED +#ifdef TOOLS_ENABLED + if (p_function->parameters[i]->default_value && p_function->parameters[i]->default_value->is_constant) { + p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } +#endif // TOOLS_ENABLED + } + + if (p_function->identifier->name == "_init") { + // 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()); + p_suite->datatype.type_source = GDScriptParser::DataType::INFERRED; + } + 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 if (all_is_constant) { + args.write[i] = call->arguments[i]->reduced_value; + } + + GDScriptParser::DataType arg_type = call->arguments[i]->get_datatype(); + if (!arg_type.is_variant()) { + if (arg_type.kind != GDScriptParser::DataType::BUILTIN) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } else if (arg_type.builtin_type != Variant::INT && arg_type.builtin_type != Variant::FLOAT) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } + } + } + + 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; + } + } + + if (p_for->list->is_constant) { + p_for->list->set_datatype(type_from_variant(p_for->list->reduced_value, p_for->list)); + } else { + GDScriptParser::DataType list_type; + list_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + list_type.kind = GDScriptParser::DataType::BUILTIN; + list_type.builtin_type = Variant::ARRAY; + p_for->list->set_datatype(list_type); + } + } + } + } + + 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)) { + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(p_variable->initializer); + } +#ifdef DEBUG_ENABLED + } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { + parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#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; + + if (p_constant->initializer != nullptr) { + reduce_expression(p_constant->initializer); + if (p_constant->initializer->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer)); + } else if (p_constant->initializer->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_constant->initializer)); + } + + if (!p_constant->initializer->is_constant) { + push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer); + } + + 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_expression(p_assert->message); + if (!p_assert->message->is_constant || p_assert->message->reduced_value.get_type() != Variant::STRING) { + push_error(R"(Expected constant string for assert error message.)", 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_parameter(GDScriptParser::ParameterNode *p_parameter) { + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::VARIANT; + + if (p_parameter->default_value != nullptr) { + reduce_expression(p_parameter->default_value); + result = p_parameter->default_value->get_datatype(); + if (p_parameter->infer_datatype) { + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else { + result.type_source = GDScriptParser::DataType::INFERRED; + } + result.is_constant = false; + } + + if (p_parameter->datatype_specifier != nullptr) { + 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 parameter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); + } else if (p_parameter->default_value->get_datatype().is_variant()) { + mark_node_unsafe(p_parameter); + } + } + } + + 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) { + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element = p_array->elements[i]; + reduce_expression(element); + } + + // 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, p_assignment->assigned_value); + } + + if (compatible) { + compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true); + if (!compatible) { + if (p_assignment->assignee->get_datatype().is_hard_type()) { + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) { + push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + } else { + // TODO: Add warning. + mark_node_unsafe(p_assignment); + } + } else { + // TODO: Warning in this case. + mark_node_unsafe(p_assignment); + } + } + } else { + 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, p_binary_op)); + + 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, p_binary_op); + + if (!valid) { + push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); + } + } 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); + + 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". + } + + bool safe_to_fold = true; + switch (builtin_type) { + // Those are stored by reference so not suited for compile-time construction. + // Because in this case they would be the same reference in all constructed values. + case Variant::OBJECT: + case Variant::DICTIONARY: + case Variant::ARRAY: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + safe_to_fold = false; + break; + default: + break; + } + + if (all_is_constant && safe_to_fold) { + // Construct here. + Vector<const Variant *> args; + for (int i = 0; i < p_call->arguments.size(); i++) { + args.push_back(&(p_call->arguments[i]->reduced_value)); + } + + Callable::CallError err; + Variant value; + Variant::construct(builtin_type, value, (const Variant **)args.ptr(), args.size(), err); + + switch (err.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + 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(); + } + signature += ")"; + push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); + } break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + 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(), true)) { + 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(); + } + signature += ")"; + 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 (GDScriptUtilityFunctions::function_exists(function_name)) { + MethodInfo function_info = GDScriptUtilityFunctions::get_function_info(function_name); + + if (all_is_constant && GDScriptUtilityFunctions::is_function_constant(function_name)) { + // 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; + GDScriptUtilityFunctions::get_function(function_name)(&value, (const Variant **)args.ptr(), args.size(), 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.)*", function_name, 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".)", function_name), 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.)*", function_name, 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.)*", function_name, 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; + } else if (Variant::has_utility_function(function_name)) { + MethodInfo function_info = info_from_utility_func(function_name); + + if (all_is_constant && Variant::get_utility_function_type(function_name) == Variant::UTILITY_FUNC_TYPE_MATH) { + // 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; + Variant::call_utility_function(function_name, &value, (const Variant **)args.ptr(), args.size(), 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.)*", function_name, 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".)", function_name), 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.)*", function_name, 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.)*", function_name, 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 && !(base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN)) { + 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 || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) { + 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) { + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) { + reduce_expression(element.key); + } + reduce_expression(element.value); + + if (element.key->is_constant) { + if (elements.has(element.key->reduced_value)) { + push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key); + } else { + elements[element.key->reduced_value] = element.value; + } + } + } + + // 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, const GDScriptParser::Node *p_source) { + Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(p_class_name)); + Error err = ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + + if (err) { + push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source); + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::UNDETECTED; + type.kind = GDScriptParser::DataType::VARIANT; + return type; + } + + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + 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, p_identifier)); + } else { + push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); + } + } else { + switch (base.builtin_type) { + case Variant::NIL: { + push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + return; + } + case Variant::DICTIONARY: { + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_identifier->set_datatype(dummy); + return; + } + default: { + Callable::CallError temp; + Variant dummy; + Variant::construct(base.builtin_type, dummy, nullptr, 0, temp); + List<PropertyInfo> properties; + dummy.get_property_list(&properties); + for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == name) { + p_identifier->set_datatype(type_from_property(prop)); + return; + } + } + push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + } + } + } + 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, p_identifier)); + return; + } + } +} + +void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) { + // TODO: This is opportunity to further infer types. + + // Check if we are inside and enum. This allows enum values to access other elements of the same enum. + if (current_enum) { + for (int i = 0; i < current_enum->values.size(); i++) { + const GDScriptParser::EnumNode::Value &element = current_enum->values[i]; + if (element.identifier->name == p_identifier->name) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::INT; + type.is_constant = true; + if (element.parent_enum->identifier) { + type.enum_type = element.parent_enum->identifier->name; + } + p_identifier->set_datatype(type); + + if (element.resolved) { + p_identifier->is_constant = true; + p_identifier->reduced_value = element.value; + } else { + push_error(R"(Cannot use another enum element before it was declared.)", p_identifier); + } + return; // Found anyway. + } + } + } + + // Check if identifier is local. + // If that's the case, the declaration already was solved before. + switch (p_identifier->source) { + 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. We make an exception for Variant::OBJECT because it's the base class for + // non-builtin types so we allow doing e.g. Object.new() + Variant::Type builtin_type = GDScriptParser::get_builtin_type(name); + if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) { + if (can_be_builtin) { + p_identifier->set_datatype(make_builtin_meta_type(builtin_type)); + 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, p_identifier)); + return; + } + + // Try singletons. + // Do this before globals because this might be a singleton loading another one before it's compiled. + if (ProjectSettings::get_singleton()->has_autoload(name)) { + const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(name); + if (autoload.is_singleton) { + // Singleton exists, so it's at least a Node. + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::NATIVE; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { + Ref<GDScriptParserRef> parser = get_parser_for(autoload.path); + if (parser.is_valid()) { + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(parser->get_parser()->head->get_datatype()); + } + } + } + result.is_constant = true; + p_identifier->set_datatype(result); + return; + } + } + + if (GDScriptLanguage::get_singleton()->get_global_map().has(name)) { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[name]; + Variant constant = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + p_identifier->set_datatype(type_from_variant(constant, p_identifier)); + 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)); + p_identifier->is_constant = true; + p_identifier->reduced_value = constant; + return; + } + + // Not found. + // Check if it's a builtin function. + if (GDScriptUtilityFunctions::function_exists(name)) { + 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, p_literal)); +} + +void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { + if (!p_preload->path) { + return; + } + + reduce_expression(p_preload->path); + + if (!p_preload->path->is_constant) { + push_error("Preloaded path must be a constant string.", p_preload->path); + return; + } + + if (p_preload->path->reduced_value.get_type() != Variant::STRING) { + push_error("Preloaded path must be a constant string.", p_preload->path); + } else { + p_preload->resolved_path = p_preload->path->reduced_value; + // TODO: Save this as script dependency. + if (p_preload->resolved_path.is_rel_path()) { + p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); + } + p_preload->resolved_path = p_preload->resolved_path.simplify_path(); + if (!FileAccess::exists(p_preload->resolved_path)) { + push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + } else { + // TODO: Don't load if validating: use completion cache. + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } + } + } + + p_preload->is_constant = true; + p_preload->reduced_value = p_preload->resource; + p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload)); +} + +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, p_subscript); + } + 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, p_subscript); + } + 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 = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; + + switch (base_type.builtin_type) { + // Can't index at all. + case Variant::RID: + case Variant::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 == nullptr) { + result.kind = GDScriptParser::DataType::VARIANT; + p_unary_op->set_datatype(result); + return; + } + + if (p_unary_op->operand->is_constant) { + p_unary_op->is_constant = true; + p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); + result = type_from_variant(p_unary_op->reduced_value, p_unary_op); + } else if (p_unary_op->operand->get_datatype().is_variant()) { + result.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_unary_op); + } else { + bool valid = false; + result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), valid, p_unary_op); + + if (!valid) { + push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); + } + } + + p_unary_op->set_datatype(result); +} + +void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { + bool all_is_constant = true; + + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element = p_array->elements[i]; + all_is_constant = all_is_constant && element->is_constant; + if (!all_is_constant) { + return; + } + } + + Array array; + array.resize(p_array->elements.size()); + for (int i = 0; i < p_array->elements.size(); i++) { + array[i] = p_array->elements[i]->reduced_value; + } + p_array->is_constant = true; + p_array->reduced_value = array; +} + +void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { + bool all_is_constant = true; + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; + if (!all_is_constant) { + return; + } + } + + Dictionary dict; + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + dict[element.key->reduced_value] = element.value->reduced_value; + } + p_dictionary->is_constant = true; + p_dictionary->reduced_value = dict; +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { + GDScriptParser::DataType result; + result.is_constant = true; + result.kind = GDScriptParser::DataType::BUILTIN; + 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()) { + if (scr->is_valid()) { + result.script_type = scr; + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } + + Ref<GDScriptParserRef> ref = get_parser_for(current->path); + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + + GDScriptParser::ClassNode *found = ref->get_parser()->head; + + // It should be okay to assume this exists, since we have a complete script already. + for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) { + found = found->get_member(E->get()).m_class; + } + + result.class_type = found; + result.script_path = ref->get_parser()->script_path; + } else { + result.kind = GDScriptParser::DataType::SCRIPT; + } + result.native_type = scr->get_instance_base_type(); + } else { + push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); + result.kind = GDScriptParser::DataType::VARIANT; + result.type_source = GDScriptParser::DataType::UNDETECTED; + result.is_meta_type = false; + } + } 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, dummy, 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 (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + if (!is_type_compatible(arg_type, par_type)) { + 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, bool &r_valid, const GDScriptParser::Node *p_source) { + // Unary version. + GDScriptParser::DataType nil_type; + nil_type.builtin_type = Variant::NIL; + return get_operation_type(p_operation, p_a, nil_type, r_valid, p_source); +} + +GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) { + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::VARIANT; + + Variant::Type a_type = p_a.builtin_type; + Variant::Type b_type = p_b.builtin_type; + + Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); + + if (op_eval == nullptr) { + r_valid = false; + return result; + } + + r_valid = true; + + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); + + 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.is_empty() ? OK : ERR_PARSE_ERROR; +} + +Error GDScriptAnalyzer::resolve_body() { + resolve_class_body(parser->head); + return parser->errors.is_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()) { + if (depended_parsers[E->get()].is_null()) { + return ERR_PARSE_ERROR; + } + depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED); + } + return parser->errors.is_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..dab5b032a3 --- /dev/null +++ b/modules/gdscript/gdscript_analyzer.h @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* gdscript_analyzer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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/object.h" +#include "core/object/reference.h" +#include "core/templates/set.h" +#include "gdscript_cache.h" +#include "gdscript_parser.h" + +class GDScriptAnalyzer { + GDScriptParser *parser = nullptr; + HashMap<String, Ref<GDScriptParserRef>> depended_parsers; + + const GDScriptParser::EnumNode *current_enum = nullptr; + + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); + GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); + + 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_parameter(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); + + void const_fold_array(GDScriptParser::ArrayNode *p_array); + void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary); + + // Helpers. + GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); + GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; + GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; + GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); + bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); + bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); + GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); + GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); + bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; + void push_error(const String &p_message, const GDScriptParser::Node *p_origin); + void mark_node_unsafe(const GDScriptParser::Node *p_node); + 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_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp new file mode 100644 index 0000000000..58c6b31a77 --- /dev/null +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -0,0 +1,1282 @@ +/*************************************************************************/ +/* gdscript_byte_codegen.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_byte_codegen.h" + +#include "core/debugger/engine_debugger.h" +#include "gdscript.h" + +uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) { +#ifdef TOOLS_ENABLED + function->arg_names.push_back(p_name); +#endif + function->_argument_count++; + function->argument_types.push_back(p_type); + if (p_is_optional) { + function->_default_arg_count++; + } + + return add_local(p_name, p_type); +} + +uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) { + int stack_pos = increase_stack(); + add_stack_identifier(p_name, stack_pos); + return stack_pos; +} + +uint32_t GDScriptByteCodeGenerator::add_local_constant(const StringName &p_name, const Variant &p_constant) { + int index = add_or_get_constant(p_constant); + local_constants[p_name] = index; + return index; +} + +uint32_t GDScriptByteCodeGenerator::add_or_get_constant(const Variant &p_constant) { + if (constant_map.has(p_constant)) { + return constant_map[p_constant]; + } + int index = constant_map.size(); + constant_map[p_constant] = index; + return index; +} + +uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) { + return get_name_map_pos(p_name); +} + +uint32_t GDScriptByteCodeGenerator::add_temporary() { + current_temporaries++; + int idx = increase_stack(); +#ifdef DEBUG_ENABLED + temp_stack.push_back(idx); +#endif + return idx; +} + +void GDScriptByteCodeGenerator::pop_temporary() { + ERR_FAIL_COND(current_temporaries == 0); + current_stack_size--; +#ifdef DEBUG_ENABLED + if (temp_stack.back()->get() != current_stack_size) { + ERR_PRINT("Mismatched popping of temporary value"); + } + temp_stack.pop_back(); +#endif + current_temporaries--; +} + +void GDScriptByteCodeGenerator::start_parameters() { + if (function->_default_arg_count > 0) { + append(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); + function->default_arguments.push_back(opcodes.size()); + } +} + +void GDScriptByteCodeGenerator::end_parameters() { + function->default_arguments.invert(); +} + +void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) { + function = memnew(GDScriptFunction); + debug_stack = EngineDebugger::is_active(); + + function->name = p_function_name; + function->_script = p_script; + function->source = p_script->get_path(); + +#ifdef DEBUG_ENABLED + function->func_cname = (String(function->source) + " - " + String(p_function_name)).utf8(); + function->_func_cname = function->func_cname.get_data(); +#endif + + function->_static = p_static; + function->return_type = p_return_type; + function->rpc_mode = p_rpc_mode; + function->_argument_count = 0; +} + +GDScriptFunction *GDScriptByteCodeGenerator::write_end() { +#ifdef DEBUG_ENABLED + if (current_temporaries != 0) { + ERR_PRINT("Non-zero temporary variables at end of function: " + itos(current_temporaries)); + } +#endif + append(GDScriptFunction::OPCODE_END, 0); + + if (constant_map.size()) { + function->_constant_count = constant_map.size(); + function->constants.resize(constant_map.size()); + function->_constants_ptr = function->constants.ptrw(); + const Variant *K = nullptr; + while ((K = constant_map.next(K))) { + int idx = constant_map[*K]; + function->constants.write[idx] = *K; + } + } else { + function->_constants_ptr = nullptr; + function->_constant_count = 0; + } + + if (name_map.size()) { + function->global_names.resize(name_map.size()); + function->_global_names_ptr = &function->global_names[0]; + for (Map<StringName, int>::Element *E = name_map.front(); E; E = E->next()) { + function->global_names.write[E->get()] = E->key(); + } + function->_global_names_count = function->global_names.size(); + + } else { + function->_global_names_ptr = nullptr; + function->_global_names_count = 0; + } + + if (opcodes.size()) { + function->code = opcodes; + function->_code_ptr = &function->code[0]; + function->_code_size = opcodes.size(); + + } else { + function->_code_ptr = nullptr; + function->_code_size = 0; + } + + if (function->default_arguments.size()) { + function->_default_arg_count = function->default_arguments.size() - 1; + function->_default_arg_ptr = &function->default_arguments[0]; + } else { + function->_default_arg_count = 0; + function->_default_arg_ptr = nullptr; + } + + if (operator_func_map.size()) { + function->operator_funcs.resize(operator_func_map.size()); + function->_operator_funcs_count = function->operator_funcs.size(); + function->_operator_funcs_ptr = function->operator_funcs.ptr(); + for (const Map<Variant::ValidatedOperatorEvaluator, int>::Element *E = operator_func_map.front(); E; E = E->next()) { + function->operator_funcs.write[E->get()] = E->key(); + } + } else { + function->_operator_funcs_count = 0; + function->_operator_funcs_ptr = nullptr; + } + + if (setters_map.size()) { + function->setters.resize(setters_map.size()); + function->_setters_count = function->setters.size(); + function->_setters_ptr = function->setters.ptr(); + for (const Map<Variant::ValidatedSetter, int>::Element *E = setters_map.front(); E; E = E->next()) { + function->setters.write[E->get()] = E->key(); + } + } else { + function->_setters_count = 0; + function->_setters_ptr = nullptr; + } + + if (getters_map.size()) { + function->getters.resize(getters_map.size()); + function->_getters_count = function->getters.size(); + function->_getters_ptr = function->getters.ptr(); + for (const Map<Variant::ValidatedGetter, int>::Element *E = getters_map.front(); E; E = E->next()) { + function->getters.write[E->get()] = E->key(); + } + } else { + function->_getters_count = 0; + function->_getters_ptr = nullptr; + } + + if (keyed_setters_map.size()) { + function->keyed_setters.resize(keyed_setters_map.size()); + function->_keyed_setters_count = function->keyed_setters.size(); + function->_keyed_setters_ptr = function->keyed_setters.ptr(); + for (const Map<Variant::ValidatedKeyedSetter, int>::Element *E = keyed_setters_map.front(); E; E = E->next()) { + function->keyed_setters.write[E->get()] = E->key(); + } + } else { + function->_keyed_setters_count = 0; + function->_keyed_setters_ptr = nullptr; + } + + if (keyed_getters_map.size()) { + function->keyed_getters.resize(keyed_getters_map.size()); + function->_keyed_getters_count = function->keyed_getters.size(); + function->_keyed_getters_ptr = function->keyed_getters.ptr(); + for (const Map<Variant::ValidatedKeyedGetter, int>::Element *E = keyed_getters_map.front(); E; E = E->next()) { + function->keyed_getters.write[E->get()] = E->key(); + } + } else { + function->_keyed_getters_count = 0; + function->_keyed_getters_ptr = nullptr; + } + + if (indexed_setters_map.size()) { + function->indexed_setters.resize(indexed_setters_map.size()); + function->_indexed_setters_count = function->indexed_setters.size(); + function->_indexed_setters_ptr = function->indexed_setters.ptr(); + for (const Map<Variant::ValidatedIndexedSetter, int>::Element *E = indexed_setters_map.front(); E; E = E->next()) { + function->indexed_setters.write[E->get()] = E->key(); + } + } else { + function->_indexed_setters_count = 0; + function->_indexed_setters_ptr = nullptr; + } + + if (indexed_getters_map.size()) { + function->indexed_getters.resize(indexed_getters_map.size()); + function->_indexed_getters_count = function->indexed_getters.size(); + function->_indexed_getters_ptr = function->indexed_getters.ptr(); + for (const Map<Variant::ValidatedIndexedGetter, int>::Element *E = indexed_getters_map.front(); E; E = E->next()) { + function->indexed_getters.write[E->get()] = E->key(); + } + } else { + function->_indexed_getters_count = 0; + function->_indexed_getters_ptr = nullptr; + } + + if (builtin_method_map.size()) { + function->builtin_methods.resize(builtin_method_map.size()); + function->_builtin_methods_ptr = function->builtin_methods.ptr(); + function->_builtin_methods_count = builtin_method_map.size(); + for (const Map<Variant::ValidatedBuiltInMethod, int>::Element *E = builtin_method_map.front(); E; E = E->next()) { + function->builtin_methods.write[E->get()] = E->key(); + } + } else { + function->_builtin_methods_ptr = nullptr; + function->_builtin_methods_count = 0; + } + + if (constructors_map.size()) { + function->constructors.resize(constructors_map.size()); + function->_constructors_ptr = function->constructors.ptr(); + function->_constructors_count = constructors_map.size(); + for (const Map<Variant::ValidatedConstructor, int>::Element *E = constructors_map.front(); E; E = E->next()) { + function->constructors.write[E->get()] = E->key(); + } + } else { + function->_constructors_ptr = nullptr; + function->_constructors_count = 0; + } + + if (utilities_map.size()) { + function->utilities.resize(utilities_map.size()); + function->_utilities_ptr = function->utilities.ptr(); + function->_utilities_count = utilities_map.size(); + for (const Map<Variant::ValidatedUtilityFunction, int>::Element *E = utilities_map.front(); E; E = E->next()) { + function->utilities.write[E->get()] = E->key(); + } + } else { + function->_utilities_ptr = nullptr; + function->_utilities_count = 0; + } + + if (gds_utilities_map.size()) { + function->gds_utilities.resize(gds_utilities_map.size()); + function->_gds_utilities_ptr = function->gds_utilities.ptr(); + function->_gds_utilities_count = gds_utilities_map.size(); + for (const Map<GDScriptUtilityFunctions::FunctionPtr, int>::Element *E = gds_utilities_map.front(); E; E = E->next()) { + function->gds_utilities.write[E->get()] = E->key(); + } + } else { + function->_gds_utilities_ptr = nullptr; + function->_gds_utilities_count = 0; + } + + if (method_bind_map.size()) { + function->methods.resize(method_bind_map.size()); + function->_methods_ptr = function->methods.ptrw(); + function->_methods_count = method_bind_map.size(); + for (const Map<MethodBind *, int>::Element *E = method_bind_map.front(); E; E = E->next()) { + function->methods.write[E->get()] = E->key(); + } + } else { + function->_methods_ptr = nullptr; + function->_methods_count = 0; + } + + if (debug_stack) { + function->stack_debug = stack_debug; + } + function->_stack_size = stack_max; + function->_instruction_args_size = instr_args_max; + function->_ptrcall_args_size = ptrcall_max; + + ended = true; + return function; +} + +#ifdef DEBUG_ENABLED +void GDScriptByteCodeGenerator::set_signature(const String &p_signature) { + function->profile.signature = p_signature; +} +#endif + +void GDScriptByteCodeGenerator::set_initial_line(int p_line) { + function->_initial_line = p_line; +} + +#define HAS_BUILTIN_TYPE(m_var) \ + (m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN) + +#define IS_BUILTIN_TYPE(m_var, m_type) \ + (m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN && m_var.type.builtin_type == m_type) + +void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) { + if (HAS_BUILTIN_TYPE(p_left_operand)) { + // Gather specific operator. + Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, Variant::NIL); + + append(GDScriptFunction::OPCODE_OPERATOR_VALIDATED, 3); + append(p_left_operand); + append(Address()); + append(p_target); + append(op_func); + return; + } + + // No specific types, perform variant evaluation. + append(GDScriptFunction::OPCODE_OPERATOR, 3); + append(p_left_operand); + append(Address()); + append(p_target); + append(p_operator); +} + +void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) { + if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand)) { + // Gather specific operator. + Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type); + + append(GDScriptFunction::OPCODE_OPERATOR_VALIDATED, 3); + append(p_left_operand); + append(p_right_operand); + append(p_target); + append(op_func); + return; + } + + // No specific types, perform variant evaluation. + append(GDScriptFunction::OPCODE_OPERATOR, 3); + append(p_left_operand); + append(p_right_operand); + append(p_target); + append(p_operator); +} + +void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) { + append(GDScriptFunction::OPCODE_EXTENDS_TEST, 3); + append(p_source); + append(p_type); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) { + append(GDScriptFunction::OPCODE_IS_BUILTIN, 3); + append(p_source); + append(p_target); + append(p_type); +} + +void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append(p_left_operand); + logic_op_jump_pos1.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_and_right_operand(const Address &p_right_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append(p_right_operand); + logic_op_jump_pos2.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_end_and(const Address &p_target) { + // If here means both operands are true. + append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); + append(p_target); + // Jump away from the fail condition. + append(GDScriptFunction::OPCODE_JUMP, 0); + append(opcodes.size() + 3); + // Here it means one of operands is false. + patch_jump(logic_op_jump_pos1.back()->get()); + patch_jump(logic_op_jump_pos2.back()->get()); + logic_op_jump_pos1.pop_back(); + logic_op_jump_pos2.pop_back(); + append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_or_left_operand(const Address &p_left_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF, 1); + append(p_left_operand); + logic_op_jump_pos1.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_or_right_operand(const Address &p_right_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF, 1); + append(p_right_operand); + logic_op_jump_pos2.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_end_or(const Address &p_target) { + // If here means both operands are false. + append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); + append(p_target); + // Jump away from the success condition. + append(GDScriptFunction::OPCODE_JUMP, 0); + append(opcodes.size() + 3); + // Here it means one of operands is true. + patch_jump(logic_op_jump_pos1.back()->get()); + patch_jump(logic_op_jump_pos2.back()->get()); + logic_op_jump_pos1.pop_back(); + logic_op_jump_pos2.pop_back(); + append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_start_ternary(const Address &p_target) { + ternary_result.push_back(p_target); +} + +void GDScriptByteCodeGenerator::write_ternary_condition(const Address &p_condition) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append(p_condition); + ternary_jump_fail_pos.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_ternary_true_expr(const Address &p_expr) { + append(GDScriptFunction::OPCODE_ASSIGN, 2); + append(ternary_result.back()->get()); + append(p_expr); + // Jump away from the false path. + append(GDScriptFunction::OPCODE_JUMP, 0); + ternary_jump_skip_pos.push_back(opcodes.size()); + append(0); + // Fail must jump here. + patch_jump(ternary_jump_fail_pos.back()->get()); + ternary_jump_fail_pos.pop_back(); +} + +void GDScriptByteCodeGenerator::write_ternary_false_expr(const Address &p_expr) { + append(GDScriptFunction::OPCODE_ASSIGN, 2); + append(ternary_result.back()->get()); + append(p_expr); +} + +void GDScriptByteCodeGenerator::write_end_ternary() { + patch_jump(ternary_jump_skip_pos.back()->get()); + ternary_jump_skip_pos.pop_back(); +} + +void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address &p_index, const Address &p_source) { + if (HAS_BUILTIN_TYPE(p_target)) { + if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_setter(p_target.type.builtin_type)) { + // Use indexed setter instead. + Variant::ValidatedIndexedSetter setter = Variant::get_member_validated_indexed_setter(p_target.type.builtin_type); + append(GDScriptFunction::OPCODE_SET_INDEXED_VALIDATED, 3); + append(p_target); + append(p_index); + append(p_source); + append(setter); + return; + } else if (Variant::get_member_validated_keyed_setter(p_target.type.builtin_type)) { + Variant::ValidatedKeyedSetter setter = Variant::get_member_validated_keyed_setter(p_target.type.builtin_type); + append(GDScriptFunction::OPCODE_SET_KEYED_VALIDATED, 3); + append(p_target); + append(p_index); + append(p_source); + append(setter); + return; + } + } + + append(GDScriptFunction::OPCODE_SET_KEYED, 3); + append(p_target); + append(p_index); + append(p_source); +} + +void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address &p_index, const Address &p_source) { + if (HAS_BUILTIN_TYPE(p_source)) { + if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_getter(p_source.type.builtin_type)) { + // Use indexed getter instead. + Variant::ValidatedIndexedGetter getter = Variant::get_member_validated_indexed_getter(p_source.type.builtin_type); + append(GDScriptFunction::OPCODE_GET_INDEXED_VALIDATED, 3); + append(p_source); + append(p_index); + append(p_target); + append(getter); + return; + } else if (Variant::get_member_validated_keyed_getter(p_source.type.builtin_type)) { + Variant::ValidatedKeyedGetter getter = Variant::get_member_validated_keyed_getter(p_source.type.builtin_type); + append(GDScriptFunction::OPCODE_GET_KEYED_VALIDATED, 3); + append(p_source); + append(p_index); + append(p_target); + append(getter); + return; + } + } + append(GDScriptFunction::OPCODE_GET_KEYED, 3); + append(p_source); + append(p_index); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) { + if (HAS_BUILTIN_TYPE(p_target) && Variant::get_member_validated_setter(p_target.type.builtin_type, p_name)) { + Variant::ValidatedSetter setter = Variant::get_member_validated_setter(p_target.type.builtin_type, p_name); + append(GDScriptFunction::OPCODE_SET_NAMED_VALIDATED, 2); + append(p_target); + append(p_source); + append(setter); + return; + } + append(GDScriptFunction::OPCODE_SET_NAMED, 2); + append(p_target); + append(p_source); + append(p_name); +} + +void GDScriptByteCodeGenerator::write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) { + if (HAS_BUILTIN_TYPE(p_source) && Variant::get_member_validated_getter(p_source.type.builtin_type, p_name)) { + Variant::ValidatedGetter getter = Variant::get_member_validated_getter(p_source.type.builtin_type, p_name); + append(GDScriptFunction::OPCODE_GET_NAMED_VALIDATED, 2); + append(p_source); + append(p_target); + append(getter); + return; + } + append(GDScriptFunction::OPCODE_GET_NAMED, 2); + append(p_source); + append(p_target); + append(p_name); +} + +void GDScriptByteCodeGenerator::write_set_member(const Address &p_value, const StringName &p_name) { + append(GDScriptFunction::OPCODE_SET_MEMBER, 1); + append(p_value); + append(p_name); +} + +void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const StringName &p_name) { + append(GDScriptFunction::OPCODE_GET_MEMBER, 1); + append(p_target); + append(p_name); +} + +void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { + if (p_target.type.has_type && !p_source.type.has_type) { + // Typed assignment. + switch (p_target.type.kind) { + case GDScriptDataType::BUILTIN: { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + append(p_target); + append(p_source); + append(p_target.type.builtin_type); + } break; + case GDScriptDataType::NATIVE: { + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3); + append(p_target); + append(p_source); + append(class_idx); + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_target.type.script_type; + int idx = get_constant_pos(script); + idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3); + append(p_target); + append(p_source); + append(idx); + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment + append(GDScriptFunction::OPCODE_ASSIGN, 2); + append(p_target); + append(p_source); + } + } + } else { + if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { + // Need conversion.. + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + append(p_target); + append(p_source); + append(p_target.type.builtin_type); + } else { + // Either untyped assignment or already type-checked by the parser + append(GDScriptFunction::OPCODE_ASSIGN, 2); + append(p_target); + append(p_source); + } + } +} + +void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) { + append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_assign_false(const Address &p_target) { + append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_dst, const Address &p_src) { + write_assign(p_dst, p_src); + function->default_arguments.push_back(opcodes.size()); +} + +void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { + int index = 0; + + switch (p_type.kind) { + case GDScriptDataType::BUILTIN: { + append(GDScriptFunction::OPCODE_CAST_TO_BUILTIN, 2); + index = p_type.builtin_type; + } break; + case GDScriptDataType::NATIVE: { + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::OPCODE_CAST_TO_NATIVE, 3); + index = class_idx; + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_type.script_type; + int idx = get_constant_pos(script); + idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_CAST_TO_SCRIPT, 3); + index = idx; + } break; + default: { + return; + } + } + + append(p_source); + append(p_target); + append(index); +} + +void GDScriptByteCodeGenerator::write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_base); + append(p_target); + append(p_arguments.size()); + append(p_function_name); +} + +void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_SELF_BASE, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(p_function_name); +} + +void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_base); + append(p_target); + append(p_arguments.size()); + append(p_function_name); +} + +void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_GDSCRIPT_UTILITY, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(p_function); +} + +void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) { + bool is_validated = true; + if (Variant::is_utility_function_vararg(p_function)) { + is_validated = true; // Vararg works fine with any argument, since they can be any type. + } else if (p_arguments.size() == Variant::get_utility_function_argument_count(p_function)) { + bool all_types_exact = true; + for (int i = 0; i < p_arguments.size(); i++) { + if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_utility_function_argument_type(p_function, i))) { + all_types_exact = false; + break; + } + } + + is_validated = all_types_exact; + } + + if (is_validated) { + append(GDScriptFunction::OPCODE_CALL_UTILITY_VALIDATED, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(Variant::get_validated_utility_function(p_function)); + } else { + append(GDScriptFunction::OPCODE_CALL_UTILITY, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(p_function); + } +} + +void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { + bool is_validated = false; + + // Check if all types are correct. + if (Variant::is_builtin_method_vararg(p_type, p_method)) { + is_validated = true; // Vararg works fine with any argument, since they can be any type. + } else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) { + bool all_types_exact = true; + for (int i = 0; i < p_arguments.size(); i++) { + if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_builtin_method_argument_type(p_type, p_method, i))) { + all_types_exact = false; + break; + } + } + + is_validated = all_types_exact; + } + + if (!is_validated) { + // Perform regular call. + write_call(p_target, p_base, p_method, p_arguments); + return; + } + + append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); + + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_base); + append(p_target); + append(p_arguments.size()); + append(Variant::get_validated_builtin_method(p_type, p_method)); +} + +void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_base); + append(p_target); + append(p_arguments.size()); + append(p_method); +} + +void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { +#define CASE_TYPE(m_type) \ + case Variant::m_type: \ + append(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \ + break + + bool is_ptrcall = true; + + if (p_method->has_return()) { + MethodInfo info; + ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info); + switch (info.return_val.type) { + CASE_TYPE(BOOL); + CASE_TYPE(INT); + CASE_TYPE(FLOAT); + CASE_TYPE(STRING); + CASE_TYPE(VECTOR2); + CASE_TYPE(VECTOR2I); + CASE_TYPE(RECT2); + CASE_TYPE(RECT2I); + CASE_TYPE(VECTOR3); + CASE_TYPE(VECTOR3I); + CASE_TYPE(TRANSFORM2D); + CASE_TYPE(PLANE); + CASE_TYPE(AABB); + CASE_TYPE(BASIS); + CASE_TYPE(TRANSFORM); + CASE_TYPE(COLOR); + CASE_TYPE(STRING_NAME); + CASE_TYPE(NODE_PATH); + CASE_TYPE(RID); + CASE_TYPE(QUAT); + CASE_TYPE(OBJECT); + CASE_TYPE(CALLABLE); + CASE_TYPE(SIGNAL); + CASE_TYPE(DICTIONARY); + CASE_TYPE(ARRAY); + CASE_TYPE(PACKED_BYTE_ARRAY); + CASE_TYPE(PACKED_INT32_ARRAY); + CASE_TYPE(PACKED_INT64_ARRAY); + CASE_TYPE(PACKED_FLOAT32_ARRAY); + CASE_TYPE(PACKED_FLOAT64_ARRAY); + CASE_TYPE(PACKED_STRING_ARRAY); + CASE_TYPE(PACKED_VECTOR2_ARRAY); + CASE_TYPE(PACKED_VECTOR3_ARRAY); + CASE_TYPE(PACKED_COLOR_ARRAY); + default: + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); + is_ptrcall = false; + break; + } + } else { + append(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size()); + } + + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_base); + append(p_target); + append(p_arguments.size()); + append(p_method); + if (is_ptrcall) { + alloc_ptrcall(p_arguments.size()); + } + +#undef CASE_TYPE +} + +void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + append(p_target); + append(p_arguments.size()); + append(p_function_name); +} + +void GDScriptByteCodeGenerator::write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + append(p_target); + append(p_arguments.size()); + append(p_function_name); +} + +void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_base); + append(p_target); + append(p_arguments.size()); + append(p_function_name); +} + +void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) { + // Try to find an appropriate constructor. + bool all_have_type = true; + Vector<Variant::Type> arg_types; + for (int i = 0; i < p_arguments.size(); i++) { + if (!HAS_BUILTIN_TYPE(p_arguments[i])) { + all_have_type = false; + break; + } + arg_types.push_back(p_arguments[i].type.builtin_type); + } + if (all_have_type) { + int valid_constructor = -1; + for (int i = 0; i < Variant::get_constructor_count(p_type); i++) { + if (Variant::get_constructor_argument_count(p_type, i) != p_arguments.size()) { + continue; + } + int types_correct = true; + for (int j = 0; j < arg_types.size(); j++) { + if (arg_types[j] != Variant::get_constructor_argument_type(p_type, i, j)) { + types_correct = false; + break; + } + } + if (types_correct) { + valid_constructor = i; + break; + } + } + if (valid_constructor >= 0) { + append(GDScriptFunction::OPCODE_CONSTRUCT_VALIDATED, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(Variant::get_validated_constructor(p_type, valid_constructor)); + return; + } + } + + append(GDScriptFunction::OPCODE_CONSTRUCT, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); + append(p_type); +} + +void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. +} + +void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { + append(GDScriptFunction::OPCODE_AWAIT, 1); + append(p_operand); + append(GDScriptFunction::OPCODE_AWAIT_RESUME, 1); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_if(const Address &p_condition) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append(p_condition); + if_jmp_addrs.push_back(opcodes.size()); + append(0); // Jump destination, will be patched. +} + +void GDScriptByteCodeGenerator::write_else() { + append(GDScriptFunction::OPCODE_JUMP, 0); // Jump from true if block; + int else_jmp_addr = opcodes.size(); + append(0); // Jump destination, will be patched. + + patch_jump(if_jmp_addrs.back()->get()); + if_jmp_addrs.pop_back(); + if_jmp_addrs.push_back(else_jmp_addr); +} + +void GDScriptByteCodeGenerator::write_endif() { + patch_jump(if_jmp_addrs.back()->get()); + if_jmp_addrs.pop_back(); +} + +void GDScriptByteCodeGenerator::start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) { + Address counter(Address::LOCAL_VARIABLE, add_local("@counter_pos", p_iterator_type), p_iterator_type); + Address container(Address::LOCAL_VARIABLE, add_local("@container_pos", p_list_type), p_list_type); + + // Store state. + for_counter_variables.push_back(counter); + for_container_variables.push_back(container); +} + +void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_variable, const Address &p_list) { + const Address &container = for_container_variables.back()->get(); + + // Assign container. + append(GDScriptFunction::OPCODE_ASSIGN, 2); + append(container); + append(p_list); + + for_iterator_variables.push_back(p_variable); +} + +void GDScriptByteCodeGenerator::write_for() { + const Address &iterator = for_iterator_variables.back()->get(); + const Address &counter = for_counter_variables.back()->get(); + const Address &container = for_container_variables.back()->get(); + + current_breaks_to_patch.push_back(List<int>()); + + GDScriptFunction::Opcode begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN; + GDScriptFunction::Opcode iterate_opcode = GDScriptFunction::OPCODE_ITERATE; + + if (container.type.has_type) { + if (container.type.kind == GDScriptDataType::BUILTIN) { + switch (container.type.builtin_type) { + case Variant::INT: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_INT; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_INT; + break; + case Variant::FLOAT: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_FLOAT; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_FLOAT; + break; + case Variant::VECTOR2: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR2; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR2; + break; + case Variant::VECTOR2I: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR2I; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR2I; + break; + case Variant::VECTOR3: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR3; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR3; + break; + case Variant::VECTOR3I: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_VECTOR3I; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_VECTOR3I; + break; + case Variant::STRING: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_STRING; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_STRING; + break; + case Variant::DICTIONARY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_DICTIONARY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_DICTIONARY; + break; + case Variant::ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_ARRAY; + break; + case Variant::PACKED_BYTE_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_BYTE_ARRAY; + break; + case Variant::PACKED_INT32_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_INT32_ARRAY; + break; + case Variant::PACKED_INT64_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_INT64_ARRAY; + break; + case Variant::PACKED_FLOAT32_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_FLOAT32_ARRAY; + break; + case Variant::PACKED_FLOAT64_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_FLOAT64_ARRAY; + break; + case Variant::PACKED_STRING_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_STRING_ARRAY; + break; + case Variant::PACKED_VECTOR2_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_VECTOR2_ARRAY; + break; + case Variant::PACKED_VECTOR3_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_VECTOR3_ARRAY; + break; + case Variant::PACKED_COLOR_ARRAY: + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_PACKED_COLOR_ARRAY; + break; + default: + break; + } + } else { + begin_opcode = GDScriptFunction::OPCODE_ITERATE_BEGIN_OBJECT; + iterate_opcode = GDScriptFunction::OPCODE_ITERATE_OBJECT; + } + } + + // Begin loop. + append(begin_opcode, 3); + append(counter); + append(container); + append(iterator); + for_jmp_addrs.push_back(opcodes.size()); + append(0); // End of loop address, will be patched. + append(GDScriptFunction::OPCODE_JUMP, 0); + append(opcodes.size() + 6); // Skip over 'continue' code. + + // Next iteration. + int continue_addr = opcodes.size(); + continue_addrs.push_back(continue_addr); + append(iterate_opcode, 3); + append(counter); + append(container); + append(iterator); + for_jmp_addrs.push_back(opcodes.size()); + append(0); // Jump destination, will be patched. +} + +void GDScriptByteCodeGenerator::write_endfor() { + // Jump back to loop check. + append(GDScriptFunction::OPCODE_JUMP, 0); + append(continue_addrs.back()->get()); + continue_addrs.pop_back(); + + // Patch end jumps (two of them). + for (int i = 0; i < 2; i++) { + patch_jump(for_jmp_addrs.back()->get()); + for_jmp_addrs.pop_back(); + } + + // Patch break statements. + for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + current_breaks_to_patch.pop_back(); + + // Pop state. + for_iterator_variables.pop_back(); + for_counter_variables.pop_back(); + for_container_variables.pop_back(); +} + +void GDScriptByteCodeGenerator::start_while_condition() { + current_breaks_to_patch.push_back(List<int>()); + continue_addrs.push_back(opcodes.size()); +} + +void GDScriptByteCodeGenerator::write_while(const Address &p_condition) { + // Condition check. + append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append(p_condition); + while_jmp_addrs.push_back(opcodes.size()); + append(0); // End of loop address, will be patched. +} + +void GDScriptByteCodeGenerator::write_endwhile() { + // Jump back to loop check. + append(GDScriptFunction::OPCODE_JUMP, 0); + append(continue_addrs.back()->get()); + continue_addrs.pop_back(); + + // Patch end jump. + patch_jump(while_jmp_addrs.back()->get()); + while_jmp_addrs.pop_back(); + + // Patch break statements. + for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + current_breaks_to_patch.pop_back(); +} + +void GDScriptByteCodeGenerator::start_match() { + match_continues_to_patch.push_back(List<int>()); +} + +void GDScriptByteCodeGenerator::start_match_branch() { + // Patch continue statements. + for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + match_continues_to_patch.pop_back(); + // Start a new list for next branch. + match_continues_to_patch.push_back(List<int>()); +} + +void GDScriptByteCodeGenerator::end_match() { + // Patch continue statements. + for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + match_continues_to_patch.pop_back(); +} + +void GDScriptByteCodeGenerator::write_break() { + append(GDScriptFunction::OPCODE_JUMP, 0); + current_breaks_to_patch.back()->get().push_back(opcodes.size()); + append(0); +} + +void GDScriptByteCodeGenerator::write_continue() { + append(GDScriptFunction::OPCODE_JUMP, 0); + append(continue_addrs.back()->get()); +} + +void GDScriptByteCodeGenerator::write_continue_match() { + append(GDScriptFunction::OPCODE_JUMP, 0); + match_continues_to_patch.back()->get().push_back(opcodes.size()); + append(0); +} + +void GDScriptByteCodeGenerator::write_breakpoint() { + append(GDScriptFunction::OPCODE_BREAKPOINT, 0); +} + +void GDScriptByteCodeGenerator::write_newline(int p_line) { + append(GDScriptFunction::OPCODE_LINE, 0); + append(p_line); + current_line = p_line; +} + +void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { + append(GDScriptFunction::OPCODE_RETURN, 1); + append(p_return_value); +} + +void GDScriptByteCodeGenerator::write_assert(const Address &p_test, const Address &p_message) { + append(GDScriptFunction::OPCODE_ASSERT, 2); + append(p_test); + append(p_message); +} + +void GDScriptByteCodeGenerator::start_block() { + push_stack_identifiers(); +} + +void GDScriptByteCodeGenerator::end_block() { + pop_stack_identifiers(); +} + +GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() { + if (!ended && function != nullptr) { + memdelete(function); + } +} diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h new file mode 100644 index 0000000000..1e66af269a --- /dev/null +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -0,0 +1,474 @@ +/*************************************************************************/ +/* gdscript_byte_codegen.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_BYTE_CODEGEN +#define GDSCRIPT_BYTE_CODEGEN + +#include "gdscript_codegen.h" + +#include "gdscript_function.h" +#include "gdscript_utility_functions.h" + +class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { + bool ended = false; + GDScriptFunction *function = nullptr; + bool debug_stack = false; + + Vector<int> opcodes; + List<Map<StringName, int>> stack_id_stack; + Map<StringName, int> stack_identifiers; + List<int> stack_identifiers_counts; + Map<StringName, int> local_constants; + + List<GDScriptFunction::StackDebug> stack_debug; + List<Map<StringName, int>> block_identifier_stack; + Map<StringName, int> block_identifiers; + + int current_stack_size = 0; + int current_temporaries = 0; + int current_locals = 0; + int current_line = 0; + int stack_max = 0; + int instr_args_max = 0; + int ptrcall_max = 0; + +#ifdef DEBUG_ENABLED + List<int> temp_stack; +#endif + + HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; + Map<StringName, int> name_map; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif + Map<Variant::ValidatedOperatorEvaluator, int> operator_func_map; + Map<Variant::ValidatedSetter, int> setters_map; + Map<Variant::ValidatedGetter, int> getters_map; + Map<Variant::ValidatedKeyedSetter, int> keyed_setters_map; + Map<Variant::ValidatedKeyedGetter, int> keyed_getters_map; + Map<Variant::ValidatedIndexedSetter, int> indexed_setters_map; + Map<Variant::ValidatedIndexedGetter, int> indexed_getters_map; + Map<Variant::ValidatedBuiltInMethod, int> builtin_method_map; + Map<Variant::ValidatedConstructor, int> constructors_map; + Map<Variant::ValidatedUtilityFunction, int> utilities_map; + Map<GDScriptUtilityFunctions::FunctionPtr, int> gds_utilities_map; + Map<MethodBind *, int> method_bind_map; + + // Lists since these can be nested. + List<int> if_jmp_addrs; + List<int> for_jmp_addrs; + List<Address> for_iterator_variables; + List<Address> for_counter_variables; + List<Address> for_container_variables; + List<int> while_jmp_addrs; + List<int> continue_addrs; + + // Used to patch jumps with `and` and `or` operators with short-circuit. + List<int> logic_op_jump_pos1; + List<int> logic_op_jump_pos2; + + List<Address> ternary_result; + List<int> ternary_jump_fail_pos; + List<int> ternary_jump_skip_pos; + + List<List<int>> current_breaks_to_patch; + List<List<int>> match_continues_to_patch; + + void add_stack_identifier(const StringName &p_id, int p_stackpos) { + current_locals++; + stack_identifiers[p_id] = p_stackpos; + if (debug_stack) { + block_identifiers[p_id] = p_stackpos; + GDScriptFunction::StackDebug sd; + sd.added = true; + sd.line = current_line; + sd.identifier = p_id; + sd.pos = p_stackpos; + stack_debug.push_back(sd); + } + } + + void push_stack_identifiers() { + stack_identifiers_counts.push_back(current_locals); + stack_id_stack.push_back(stack_identifiers); + if (debug_stack) { + Map<StringName, int> block_ids(block_identifiers); + block_identifier_stack.push_back(block_ids); + block_identifiers.clear(); + } + } + + void pop_stack_identifiers() { + current_locals = stack_identifiers_counts.back()->get(); + stack_identifiers_counts.pop_back(); + stack_identifiers = stack_id_stack.back()->get(); + stack_id_stack.pop_back(); +#ifdef DEBUG_ENABLED + if (current_temporaries != 0) { + ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(current_temporaries)); + } +#endif + current_stack_size = current_locals; + + if (debug_stack) { + for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { + GDScriptFunction::StackDebug sd; + sd.added = false; + sd.identifier = E->key(); + sd.line = current_line; + sd.pos = E->get(); + stack_debug.push_back(sd); + } + block_identifiers = block_identifier_stack.back()->get(); + block_identifier_stack.pop_back(); + } + } + + int get_name_map_pos(const StringName &p_identifier) { + int ret; + if (!name_map.has(p_identifier)) { + ret = name_map.size(); + name_map[p_identifier] = ret; + } else { + ret = name_map[p_identifier]; + } + return ret; + } + + int get_constant_pos(const Variant &p_constant) { + if (constant_map.has(p_constant)) + return constant_map[p_constant]; + int pos = constant_map.size(); + constant_map[p_constant] = pos; + return pos; + } + + int get_operation_pos(const Variant::ValidatedOperatorEvaluator p_operation) { + if (operator_func_map.has(p_operation)) + return operator_func_map[p_operation]; + int pos = operator_func_map.size(); + operator_func_map[p_operation] = pos; + return pos; + } + + int get_setter_pos(const Variant::ValidatedSetter p_setter) { + if (setters_map.has(p_setter)) + return setters_map[p_setter]; + int pos = setters_map.size(); + setters_map[p_setter] = pos; + return pos; + } + + int get_getter_pos(const Variant::ValidatedGetter p_getter) { + if (getters_map.has(p_getter)) + return getters_map[p_getter]; + int pos = getters_map.size(); + getters_map[p_getter] = pos; + return pos; + } + + int get_keyed_setter_pos(const Variant::ValidatedKeyedSetter p_keyed_setter) { + if (keyed_setters_map.has(p_keyed_setter)) + return keyed_setters_map[p_keyed_setter]; + int pos = keyed_setters_map.size(); + keyed_setters_map[p_keyed_setter] = pos; + return pos; + } + + int get_keyed_getter_pos(const Variant::ValidatedKeyedGetter p_keyed_getter) { + if (keyed_getters_map.has(p_keyed_getter)) + return keyed_getters_map[p_keyed_getter]; + int pos = keyed_getters_map.size(); + keyed_getters_map[p_keyed_getter] = pos; + return pos; + } + + int get_indexed_setter_pos(const Variant::ValidatedIndexedSetter p_indexed_setter) { + if (indexed_setters_map.has(p_indexed_setter)) + return indexed_setters_map[p_indexed_setter]; + int pos = indexed_setters_map.size(); + indexed_setters_map[p_indexed_setter] = pos; + return pos; + } + + int get_indexed_getter_pos(const Variant::ValidatedIndexedGetter p_indexed_getter) { + if (indexed_getters_map.has(p_indexed_getter)) + return indexed_getters_map[p_indexed_getter]; + int pos = indexed_getters_map.size(); + indexed_getters_map[p_indexed_getter] = pos; + return pos; + } + + int get_builtin_method_pos(const Variant::ValidatedBuiltInMethod p_method) { + if (builtin_method_map.has(p_method)) { + return builtin_method_map[p_method]; + } + int pos = builtin_method_map.size(); + builtin_method_map[p_method] = pos; + return pos; + } + + int get_constructor_pos(const Variant::ValidatedConstructor p_constructor) { + if (constructors_map.has(p_constructor)) { + return constructors_map[p_constructor]; + } + int pos = constructors_map.size(); + constructors_map[p_constructor] = pos; + return pos; + } + + int get_utility_pos(const Variant::ValidatedUtilityFunction p_utility) { + if (utilities_map.has(p_utility)) { + return utilities_map[p_utility]; + } + int pos = utilities_map.size(); + utilities_map[p_utility] = pos; + return pos; + } + + int get_gds_utility_pos(const GDScriptUtilityFunctions::FunctionPtr p_gds_utility) { + if (gds_utilities_map.has(p_gds_utility)) { + return gds_utilities_map[p_gds_utility]; + } + int pos = gds_utilities_map.size(); + gds_utilities_map[p_gds_utility] = pos; + return pos; + } + + int get_method_bind_pos(MethodBind *p_method) { + if (method_bind_map.has(p_method)) { + return method_bind_map[p_method]; + } + int pos = method_bind_map.size(); + method_bind_map[p_method] = pos; + return pos; + } + + void alloc_stack(int p_level) { + if (p_level >= stack_max) + stack_max = p_level + 1; + } + + int increase_stack() { + int top = current_stack_size++; + alloc_stack(current_stack_size); + return top; + } + + void alloc_ptrcall(int p_params) { + if (p_params >= ptrcall_max) + ptrcall_max = p_params; + } + + int address_of(const Address &p_address) { + switch (p_address.mode) { + case Address::SELF: + return GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS; + case Address::CLASS: + return GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS; + case Address::MEMBER: + return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); + case Address::CLASS_CONSTANT: + return p_address.address | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); + case Address::LOCAL_CONSTANT: + case Address::CONSTANT: + return p_address.address | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + case Address::LOCAL_VARIABLE: + case Address::TEMPORARY: + case Address::FUNCTION_PARAMETER: + return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + case Address::GLOBAL: + return p_address.address | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); + case Address::NAMED_GLOBAL: + return p_address.address | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + case Address::NIL: + return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; + } + return -1; // Unreachable. + } + + void append(GDScriptFunction::Opcode p_code, int p_argument_count) { + opcodes.push_back((p_code & GDScriptFunction::INSTR_MASK) | (p_argument_count << GDScriptFunction::INSTR_BITS)); + instr_args_max = MAX(instr_args_max, p_argument_count); + } + + void append(int p_code) { + opcodes.push_back(p_code); + } + + void append(const Address &p_address) { + opcodes.push_back(address_of(p_address)); + } + + void append(const StringName &p_name) { + opcodes.push_back(get_name_map_pos(p_name)); + } + + void append(const Variant::ValidatedOperatorEvaluator p_operation) { + opcodes.push_back(get_operation_pos(p_operation)); + } + + void append(const Variant::ValidatedSetter p_setter) { + opcodes.push_back(get_setter_pos(p_setter)); + } + + void append(const Variant::ValidatedGetter p_getter) { + opcodes.push_back(get_getter_pos(p_getter)); + } + + void append(const Variant::ValidatedKeyedSetter p_keyed_setter) { + opcodes.push_back(get_keyed_setter_pos(p_keyed_setter)); + } + + void append(const Variant::ValidatedKeyedGetter p_keyed_getter) { + opcodes.push_back(get_keyed_getter_pos(p_keyed_getter)); + } + + void append(const Variant::ValidatedIndexedSetter p_indexed_setter) { + opcodes.push_back(get_indexed_setter_pos(p_indexed_setter)); + } + + void append(const Variant::ValidatedIndexedGetter p_indexed_getter) { + opcodes.push_back(get_indexed_getter_pos(p_indexed_getter)); + } + + void append(const Variant::ValidatedBuiltInMethod p_method) { + opcodes.push_back(get_builtin_method_pos(p_method)); + } + + void append(const Variant::ValidatedConstructor p_constructor) { + opcodes.push_back(get_constructor_pos(p_constructor)); + } + + void append(const Variant::ValidatedUtilityFunction p_utility) { + opcodes.push_back(get_utility_pos(p_utility)); + } + + void append(const GDScriptUtilityFunctions::FunctionPtr p_gds_utility) { + opcodes.push_back(get_gds_utility_pos(p_gds_utility)); + } + + void append(MethodBind *p_method) { + opcodes.push_back(get_method_bind_pos(p_method)); + } + + void patch_jump(int p_address) { + opcodes.write[p_address] = opcodes.size(); + } + +public: + virtual uint32_t add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) override; + virtual uint32_t add_local(const StringName &p_name, const GDScriptDataType &p_type) override; + virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) override; + virtual uint32_t add_or_get_constant(const Variant &p_constant) override; + virtual uint32_t add_or_get_name(const StringName &p_name) override; + virtual uint32_t add_temporary() override; + virtual void pop_temporary() override; + + virtual void start_parameters() override; + virtual void end_parameters() override; + + virtual void start_block() override; + virtual void end_block() override; + + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) override; + virtual GDScriptFunction *write_end() override; + +#ifdef DEBUG_ENABLED + virtual void set_signature(const String &p_signature) override; +#endif + virtual void set_initial_line(int p_line) override; + + virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override; + virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override; + virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override; + virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) override; + virtual void write_and_left_operand(const Address &p_left_operand) override; + virtual void write_and_right_operand(const Address &p_right_operand) override; + virtual void write_end_and(const Address &p_target) override; + virtual void write_or_left_operand(const Address &p_left_operand) override; + virtual void write_or_right_operand(const Address &p_right_operand) override; + virtual void write_end_or(const Address &p_target) override; + virtual void write_start_ternary(const Address &p_target) override; + virtual void write_ternary_condition(const Address &p_condition) override; + virtual void write_ternary_true_expr(const Address &p_expr) override; + virtual void write_ternary_false_expr(const Address &p_expr) override; + virtual void write_end_ternary() override; + virtual void write_set(const Address &p_target, const Address &p_index, const Address &p_source) override; + virtual void write_get(const Address &p_target, const Address &p_index, const Address &p_source) override; + virtual void write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) override; + virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) override; + virtual void write_set_member(const Address &p_value, const StringName &p_name) override; + virtual void write_get_member(const Address &p_target, const StringName &p_name) override; + virtual void write_assign(const Address &p_target, const Address &p_source) override; + virtual void write_assign_true(const Address &p_target) override; + virtual void write_assign_false(const Address &p_target) override; + virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; + virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; + virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override; + virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override; + virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override; + virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_await(const Address &p_target, const Address &p_operand) override; + virtual void write_if(const Address &p_condition) override; + virtual void write_else() override; + virtual void write_endif() override; + virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override; + virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override; + virtual void write_for() override; + virtual void write_endfor() override; + virtual void start_while_condition() override; + virtual void write_while(const Address &p_condition) override; + virtual void write_endwhile() override; + virtual void start_match() override; + virtual void start_match_branch() override; + virtual void end_match() override; + virtual void write_break() override; + virtual void write_continue() override; + virtual void write_continue_match() override; + virtual void write_breakpoint() override; + virtual void write_newline(int p_line) override; + virtual void write_return(const Address &p_return_value) override; + virtual void write_assert(const Address &p_test, const Address &p_message) override; + + virtual ~GDScriptByteCodeGenerator(); +}; + +#endif // GDSCRIPT_BYTE_CODEGEN diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp new file mode 100644 index 0000000000..113d36be98 --- /dev/null +++ b/modules/gdscript/gdscript_cache.cpp @@ -0,0 +1,255 @@ +/*************************************************************************/ +/* gdscript_cache.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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/templates/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; + } + } + if (result != OK) { + if (parser != nullptr) { + memdelete(parser); + parser = nullptr; + } + if (analyzer != nullptr) { + memdelete(analyzer); + analyzer = nullptr; + } + return result; + } + } + + return result; +} + +GDScriptParserRef::~GDScriptParserRef() { + if (parser != nullptr) { + memdelete(parser); + } + if (analyzer != nullptr) { + memdelete(analyzer); + } + MutexLock lock(GDScriptCache::singleton->lock); + GDScriptCache::singleton->parser_map.erase(path); +} + +GDScriptCache *GDScriptCache::singleton = nullptr; + +void GDScriptCache::remove_script(const String &p_path) { + MutexLock lock(singleton->lock); + singleton->shallow_gdscript_cache.erase(p_path); + singleton->full_gdscript_cache.erase(p_path); +} + +Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->lock); + Ref<GDScriptParserRef> ref; + if (p_owner != String()) { + singleton->dependencies[p_owner].insert(p_path); + } + if (singleton->parser_map.has(p_path)) { + ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]); + } else { + if (!FileAccess::exists(p_path)) { + r_error = ERR_FILE_NOT_FOUND; + return ref; + } + GDScriptParser *parser = memnew(GDScriptParser); + ref.instance(); + ref->parser = parser; + ref->path = p_path; + singleton->parser_map[p_path] = ref.ptr(); + } + + 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 lock(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.ptr(); + return script; +} + +Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(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.ptr(); + 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.ptr(); + 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_functions.h b/modules/gdscript/gdscript_cache.h index 2c6dc02913..d1d2a2abbf 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_cache.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* gdscript_functions.h */ +/* 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). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -28,110 +28,70 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GDSCRIPT_FUNCTIONS_H -#define GDSCRIPT_FUNCTIONS_H +#ifndef GDSCRIPT_CACHE_H +#define GDSCRIPT_CACHE_H -#include "core/variant.h" +#include "core/object/reference.h" +#include "core/os/mutex.h" +#include "core/templates/hash_map.h" +#include "core/templates/set.h" +#include "gdscript.h" -class GDScriptFunctions { +class GDScriptAnalyzer; +class GDScriptParser; + +class GDScriptParserRef : public Reference { public: - enum Function { - MATH_SIN, - MATH_COS, - MATH_TAN, - MATH_SINH, - MATH_COSH, - MATH_TANH, - MATH_ASIN, - MATH_ACOS, - MATH_ATAN, - MATH_ATAN2, - MATH_SQRT, - MATH_FMOD, - MATH_FPOSMOD, - MATH_POSMOD, - MATH_FLOOR, - MATH_CEIL, - MATH_ROUND, - MATH_ABS, - MATH_SIGN, - MATH_POW, - MATH_LOG, - MATH_EXP, - MATH_ISNAN, - MATH_ISINF, - MATH_ISEQUALAPPROX, - MATH_ISZEROAPPROX, - MATH_EASE, - MATH_STEP_DECIMALS, - MATH_STEPIFY, - MATH_LERP, - MATH_LERP_ANGLE, - MATH_INVERSE_LERP, - MATH_RANGE_LERP, - MATH_SMOOTHSTEP, - MATH_MOVE_TOWARD, - MATH_DECTIME, - MATH_RANDOMIZE, - MATH_RAND, - MATH_RANDF, - MATH_RANDOM, - MATH_SEED, - MATH_RANDSEED, - MATH_DEG2RAD, - MATH_RAD2DEG, - MATH_LINEAR2DB, - MATH_DB2LINEAR, - MATH_POLAR2CARTESIAN, - MATH_CARTESIAN2POLAR, - MATH_WRAP, - MATH_WRAPF, - LOGIC_MAX, - LOGIC_MIN, - LOGIC_CLAMP, - LOGIC_NEAREST_PO2, - OBJ_WEAKREF, - FUNC_FUNCREF, - TYPE_CONVERT, - TYPE_OF, - TYPE_EXISTS, - TEXT_CHAR, - TEXT_ORD, - TEXT_STR, - TEXT_PRINT, - TEXT_PRINT_TABBED, - TEXT_PRINT_SPACED, - TEXT_PRINTERR, - TEXT_PRINTRAW, - TEXT_PRINT_DEBUG, - PUSH_ERROR, - PUSH_WARNING, - VAR_TO_STR, - STR_TO_VAR, - VAR_TO_BYTES, - BYTES_TO_VAR, - GEN_RANGE, - RESOURCE_LOAD, - INST2DICT, - DICT2INST, - VALIDATE_JSON, - PARSE_JSON, - TO_JSON, - HASH, - COLOR8, - COLORN, - PRINT_STACK, - GET_STACK, - INSTANCE_FROM_ID, - LEN, - IS_INSTANCE_VALID, - FUNC_MAX + enum Status { + EMPTY, + PARSED, + INHERITANCE_SOLVED, + INTERFACE_SOLVED, + FULLY_SOLVED, }; - static const char *get_func_name(Function p_func); - static void call(Function p_func, const Variant **p_args, int p_arg_count, Variant &r_ret, Callable::CallError &r_error); - static bool is_deterministic(Function p_func); - static MethodInfo get_info(Function p_func); +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, GDScriptParserRef *> parser_map; + HashMap<String, GDScript *> shallow_gdscript_cache; + HashMap<String, 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_FUNCTIONS_H +#endif // GDSCRIPT_CACHE_H diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h new file mode 100644 index 0000000000..d72bd12033 --- /dev/null +++ b/modules/gdscript/gdscript_codegen.h @@ -0,0 +1,167 @@ +/*************************************************************************/ +/* gdscript_codegen.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_CODEGEN +#define GDSCRIPT_CODEGEN + +#include "core/io/multiplayer_api.h" +#include "core/string/string_name.h" +#include "core/variant/variant.h" +#include "gdscript_function.h" +#include "gdscript_utility_functions.h" + +class GDScriptCodeGenerator { +public: + struct Address { + enum AddressMode { + SELF, + CLASS, + MEMBER, + CONSTANT, + CLASS_CONSTANT, + LOCAL_CONSTANT, + LOCAL_VARIABLE, + FUNCTION_PARAMETER, + TEMPORARY, + GLOBAL, + NAMED_GLOBAL, + NIL, + }; + AddressMode mode = NIL; + uint32_t address = 0; + GDScriptDataType type; + + Address() {} + Address(AddressMode p_mode, const GDScriptDataType &p_type = GDScriptDataType()) { + mode = p_mode; + type = p_type; + } + Address(AddressMode p_mode, uint32_t p_address, const GDScriptDataType &p_type = GDScriptDataType()) { + mode = p_mode, + address = p_address; + type = p_type; + } + }; + + virtual uint32_t add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) = 0; + virtual uint32_t add_local(const StringName &p_name, const GDScriptDataType &p_type) = 0; + virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) = 0; + virtual uint32_t add_or_get_constant(const Variant &p_constant) = 0; + virtual uint32_t add_or_get_name(const StringName &p_name) = 0; + virtual uint32_t add_temporary() = 0; + virtual void pop_temporary() = 0; + + virtual void start_parameters() = 0; + virtual void end_parameters() = 0; + + virtual void start_block() = 0; + virtual void end_block() = 0; + + // virtual int get_max_stack_level() = 0; + // virtual int get_max_function_arguments() = 0; + + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) = 0; + virtual GDScriptFunction *write_end() = 0; + +#ifdef DEBUG_ENABLED + virtual void set_signature(const String &p_signature) = 0; +#endif + virtual void set_initial_line(int p_line) = 0; + + // virtual void alloc_stack(int p_level) = 0; // Is this needed? + // virtual void alloc_call(int p_arg_count) = 0; // This might be automatic from other functions. + + virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0; + virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0; + virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0; + virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) = 0; + virtual void write_and_left_operand(const Address &p_left_operand) = 0; + virtual void write_and_right_operand(const Address &p_right_operand) = 0; + virtual void write_end_and(const Address &p_target) = 0; + virtual void write_or_left_operand(const Address &p_left_operand) = 0; + virtual void write_or_right_operand(const Address &p_right_operand) = 0; + virtual void write_end_or(const Address &p_target) = 0; + virtual void write_start_ternary(const Address &p_target) = 0; + virtual void write_ternary_condition(const Address &p_condition) = 0; + virtual void write_ternary_true_expr(const Address &p_expr) = 0; + virtual void write_ternary_false_expr(const Address &p_expr) = 0; + virtual void write_end_ternary() = 0; + virtual void write_set(const Address &p_target, const Address &p_index, const Address &p_source) = 0; + virtual void write_get(const Address &p_target, const Address &p_index, const Address &p_source) = 0; + virtual void write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0; + virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0; + virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0; + virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0; + virtual void write_assign(const Address &p_target, const Address &p_source) = 0; + virtual void write_assign_true(const Address &p_target) = 0; + virtual void write_assign_false(const Address &p_target) = 0; + virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; + virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; + virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0; + virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) = 0; + virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_await(const Address &p_target, const Address &p_operand) = 0; + virtual void write_if(const Address &p_condition) = 0; + // virtual void write_elseif(const Address &p_condition) = 0; This kind of makes things more difficult for no real benefit. + virtual void write_else() = 0; + virtual void write_endif() = 0; + virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0; + virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0; + virtual void write_for() = 0; + virtual void write_endfor() = 0; + virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation. + virtual void write_while(const Address &p_condition) = 0; + virtual void write_endwhile() = 0; + virtual void start_match() = 0; + virtual void start_match_branch() = 0; + virtual void end_match() = 0; + virtual void write_break() = 0; + virtual void write_continue() = 0; + virtual void write_continue_match() = 0; + virtual void write_breakpoint() = 0; + virtual void write_newline(int p_line) = 0; + virtual void write_return(const Address &p_return_value) = 0; + virtual void write_assert(const Address &p_test, const Address &p_message) = 0; + + virtual ~GDScriptCodeGenerator() {} +}; + +#endif // GDSCRIPT_CODEGEN diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 2bbec29043..b491440d4c 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,26 +31,29 @@ #include "gdscript_compiler.h" #include "gdscript.h" +#include "gdscript_byte_codegen.h" +#include "gdscript_cache.h" +#include "gdscript_utility_functions.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.locals.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,59 +63,22 @@ 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) - return false; - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator - codegen.opcodes.push_back(op); //which operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_a); // argument 2 (repeated) - //codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_NIL); // argument 2 (unary only takes one parameter) - return true; -} - -bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::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) - return false; - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) - p_stack_level++; //uses stack for return, increase stack - - int src_address_b = _parse_expression(codegen, on->arguments[1], p_stack_level, false, p_initializer); - if (src_address_b < 0) - return false; - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator - codegen.opcodes.push_back(op); //which operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - return true; -} - -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { - if (!p_datatype.has_type) { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const { + if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -120,6 +86,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; @@ -130,1712 +99,1948 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; - result.script_type = p_datatype.script_type; - result.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.script_type_ref = Ref<Script>(p_datatype.script_type); + result.script_type = result.script_type_ref.ptr(); result.native_type = result.script_type->get_instance_base_type(); } break; case GDScriptParser::DataType::CLASS: { // 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.is_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_ref = script; + result.script_type = result.script_type_ref.ptr(); + result.native_type = script->get_instance_base_type(); + } else { + result.kind = GDScriptDataType::GDSCRIPT; + result.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); + result.script_type = result.script_type_ref.ptr(); + result.native_type = p_datatype.native_type; } - 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(); } } + // Only hold strong reference to the script if it's not the owner of the + // element qualified with this type, to avoid cyclic references (leaks). + if (result.script_type && result.script_type == p_owner) { + result.script_type_ref = Ref<Script>(); + } + return result; } -int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::OperatorNode *p_expression, 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: { - - //none - } break; - default: { - - ERR_FAIL_V(-1); +static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataType &p_arg_type) { + if (!p_arg_type.has_type) { + return false; + } + if (p_par_type.type == Variant::NIL) { + return false; + } + if (p_par_type.type == Variant::OBJECT) { + if (p_arg_type.kind == GDScriptDataType::BUILTIN) { + return false; } + StringName class_name; + if (p_arg_type.kind == GDScriptDataType::NATIVE) { + class_name = p_arg_type.native_type; + } else { + class_name = p_arg_type.native_type == StringName() ? p_arg_type.script_type->get_instance_base_type() : p_arg_type.native_type; + } + return p_par_type.class_name == class_name || ClassDB::is_parent_class(class_name, p_par_type.class_name); + } else { + if (p_arg_type.kind != GDScriptDataType::BUILTIN) { + return false; + } + return p_par_type.type == p_arg_type.builtin_type; } +} - 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); +static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { + if (p_method->get_argument_count() != p_arguments.size()) { + // ptrcall won't work with default arguments. + return false; } - - if (!_create_binary_operator(codegen, p_expression, var_op, p_stack_level, initializer, 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 - codegen.alloc_stack(p_stack_level); - return dst_addr; + MethodInfo info; + ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info); + for (int i = 0; i < p_arguments.size(); i++) { + const PropertyInfo &prop = info.arguments[i]; + if (!_is_exact_type(prop, p_arguments[i].type)) { + return false; + } + } + return true; } -int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::Node *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { - - switch (p_expression->type) { - //should parse variable declaration and adjust stack accordingly... - case GDScriptParser::Node::TYPE_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 +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { + if (p_expression->is_constant) { + return codegen.add_constant(p_expression->reduced_value); + } - //This could be made much simpler by just indexing "self", but done this way (with custom self-addressing modes) increases performance a lot. + GDScriptCodeGenerator *gen = codegen.generator; + switch (p_expression->type) { + case GDScriptParser::Node::IDENTIFIER: { + // Look for identifiers in current scope. const GDScriptParser::IdentifierNode *in = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); StringName identifier = in->name; - // TRY STACK! - if (!p_initializer && codegen.stack_identifiers.has(identifier)) { + // Try function parameters. + if (codegen.parameters.has(identifier)) { + return codegen.parameters[identifier]; + } - int pos = codegen.stack_identifiers[identifier]; - return pos | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); + // Try local variables and constants. + if (!p_initializer && codegen.locals.has(identifier)) { + return codegen.locals[identifier]; } - // TRY CLASS MEMBER + // Try class members. if (_is_class_member_property(codegen, identifier)) { - //get property - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_MEMBER); // perform operator - codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + // Get property. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Could get the type of the class member here. + gen->write_get_member(temp, identifier); + return temp; } - //TRY MEMBERS! - if (!codegen.function_node || !codegen.function_node->_static) { - - // TRY MEMBER VARIABLES! - //static function + // Try members. + if (!codegen.function_node || !codegen.function_node->is_static) { + // Try member variables. 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. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); + Vector<GDScriptCodeGenerator::Address> args; // No argument needed. + gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); + return temp; + } else { + // No getter or inside getter: direct member access., + int idx = codegen.script->member_indices[identifier].index; + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier)); + } } } - //TRY CLASS CONSTANTS - + // Try class constants. GDScript *owner = codegen.script; while (owner) { - GDScript *scr = owner; GDScriptNativeClass *nc = nullptr; while (scr) { - if (scr->constants.has(identifier)) { - - //int idx=scr->constants[identifier]; - int idx = codegen.get_name_map_pos(identifier); - return idx | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS_CONSTANT, gen->add_or_get_name(identifier)); // TODO: Get type here. } - if (scr->native.is_valid()) + if (scr->native.is_valid()) { nc = scr->native.ptr(); + } scr = scr->_base; } - // CLASS C++ Integer Constant - + // Class C++ integer constant. if (nc) { - bool success = false; int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); if (success) { - Variant key = constant; - int idx; - - if (!codegen.constant_map.has(key)) { - - idx = codegen.constant_map.size(); - codegen.constant_map[key] = idx; - - } else { - idx = codegen.constant_map[key]; - } - - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + return codegen.add_constant(constant); } } owner = owner->_owner; } - 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. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. + GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); - int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + gen->write_get_named(temp, identifier, self); + return temp; + } } - /* TRY GLOBAL CLASSES */ + if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::GLOBAL, idx); // TODO: Get type. + } + // 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 = 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; + RES res; + if (class_node->identifier && class_node->identifier->name == identifier) { + res = Ref<GDScript>(main_script); } else { - idx = codegen.constant_map[key]; + 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); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } } - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + return codegen.add_constant(res); } #ifdef TOOLS_ENABLED if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { - - int idx = codegen.named_globals.find(identifier); - if (idx == -1) { - idx = codegen.named_globals.size(); - codegen.named_globals.push_back(identifier); - } - return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NAMED_GLOBAL, gen->add_or_get_name(identifier)); // TODO: Get type. } #endif - //not found, error - + // Not found, error. _set_error("Identifier not found: " + String(identifier), p_expression); - - return -1; - + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } break; - case GDScriptParser::Node::TYPE_CONSTANT: { - //return constant - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression); - - int idx; - - if (!codegen.constant_map.has(cn->value)) { - - idx = codegen.constant_map.size(); - codegen.constant_map[cn->value] = idx; - - } else { - idx = codegen.constant_map[cn->value]; - } - - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) + case GDScriptParser::Node::LITERAL: { + // Return constant. + const GDScriptParser::LiteralNode *cn = static_cast<const GDScriptParser::LiteralNode *>(p_expression); + return codegen.add_constant(cn->value); } 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; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } - return (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF); } break; - case GDScriptParser::Node::TYPE_ARRAY: { - + case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); - Vector<int> values; + Vector<GDScriptCodeGenerator::Address> values; - int slevel = p_stack_level; + // Create the result temporary first since it's the last to be killed. + GDScriptDataType array_type; + array_type.has_type = true; + array_type.kind = GDScriptDataType::BUILTIN; + array_type.builtin_type = Variant::ARRAY; + GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { - - int ret = _parse_expression(codegen, an->elements[i], slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + GDScriptCodeGenerator::Address val = _parse_expression(codegen, r_error, an->elements[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - - values.push_back(ret); + values.push_back(val); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY); - codegen.opcodes.push_back(values.size()); - for (int i = 0; i < values.size(); i++) - codegen.opcodes.push_back(values[i]); + gen->write_construct_array(result, values); - 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; + for (int i = 0; i < values.size(); i++) { + if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + return result; } 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<GDScriptCodeGenerator::Address> elements; - int slevel = p_stack_level; + // Create the result temporary first since it's the last to be killed. + GDScriptDataType dict_type; + dict_type.has_type = true; + dict_type.kind = GDScriptDataType::BUILTIN; + dict_type.builtin_type = Variant::DICTIONARY; + GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type); for (int i = 0; i < dn->elements.size(); i++) { - - 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. + GDScriptCodeGenerator::Address element; + switch (dn->style) { + case GDScriptParser::DictionaryNode::PYTHON_DICT: + // Python-style: key is any expression. + element = _parse_expression(codegen, r_error, dn->elements[i].key); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + break; + case GDScriptParser::DictionaryNode::LUA_TABLE: + // Lua-style: key is an identifier interpreted as string. + String key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; + element = codegen.add_constant(key); + break; } - values.push_back(ret); + elements.push_back(element); - ret = _parse_expression(codegen, dn->elements[i].value, slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + element = _parse_expression(codegen, r_error, dn->elements[i].value); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - values.push_back(ret); + elements.push_back(element); } - 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]); + gen->write_construct_dictionary(result, elements); - 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; + for (int i = 0; i < elements.size(); i++) { + if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + return result; } break; - case GDScriptParser::Node::TYPE_CAST: { + case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); - int slevel = p_stack_level; - int src_addr = _parse_expression(codegen, cn->source_node, 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); + // Create temporary for result first since it will be deleted last. + GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type); - switch (cast_type.kind) { - case GDScriptDataType::BUILTIN: { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); - codegen.opcodes.push_back(cast_type.builtin_type); - } break; - case GDScriptDataType::NATIVE: { - int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(cast_type.native_type)) { - - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cast_type.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) - } else { - _set_error("Invalid native class type '" + String(cast_type.native_type) + "'.", cn); - return -1; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator - codegen.opcodes.push_back(class_idx); // variable type - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { + GDScriptCodeGenerator::Address source = _parse_expression(codegen, r_error, cn->operand); - 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) + gen->write_cast(result, source, cast_type); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator - codegen.opcodes.push_back(idx); // variable type - } break; - default: { - _set_error("Parser bug: unresolved data type.", cn); - return -1; - } + if (source.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(src_addr); // source address - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; - + return result; } break; - 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); + case GDScriptParser::Node::CALL: { + const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); + GDScriptCodeGenerator::Address result = codegen.add_temporary(type); + + Vector<GDScriptCodeGenerator::Address> arguments; + for (int i = 0; i < call->arguments.size(); i++) { + GDScriptCodeGenerator::Address arg = _parse_expression(codegen, r_error, call->arguments[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + arguments.push_back(arg); + } - const GDScriptParser::IdentifierNode *in = (const GDScriptParser::IdentifierNode *)on->arguments[0]; + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) { + // Construct a built-in type. + Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); + + gen->write_construct(result, vtype, arguments); + } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) { + // Variant utility function. + gen->write_call_utility(result, call->function_name, arguments); + } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptUtilityFunctions::function_exists(call->function_name)) { + // GDScript utility function. + gen->write_call_gdscript_utility(result, GDScriptUtilityFunctions::get_function(call->function_name), arguments); + } else { + // Regular function. + const GDScriptParser::ExpressionNode *callee = call->callee; - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 1; i < on->arguments.size(); i++) { + if (call->is_super) { + // Super call. + gen->write_super_call(result, call->function_name, arguments); + } else { + if (callee->type == GDScriptParser::Node::IDENTIFIER) { + // Self function call. + if (ClassDB::has_method(codegen.script->native->get_name(), call->function_name)) { + // Native method, use faster path. + GDScriptCodeGenerator::Address self; + self.mode = GDScriptCodeGenerator::Address::SELF; + MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name); + + if (_have_exact_arguments(method, arguments)) { + // Exact arguments, use ptrcall. + gen->write_call_ptrcall(result, self, method, arguments); + } else { + // Not exact arguments, but still can use method bind call. + gen->write_call_method_bind(result, self, method, arguments); + } + } else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") { + GDScriptCodeGenerator::Address self; + self.mode = GDScriptCodeGenerator::Address::CLASS; + if (within_await) { + gen->write_call_async(result, self, call->function_name, arguments); + } else { + gen->write_call(result, self, call->function_name, arguments); + } + } else { + if (within_await) { + gen->write_call_self_async(result, call->function_name, arguments); + } else { + gen->write_call_self(result, call->function_name, arguments); + } + } + } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) { + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); - 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); + if (subscript->is_attribute) { + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + if (within_await) { + gen->write_call_async(result, base, call->function_name, arguments); + } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) { + // Native method, use faster path. + StringName class_name; + if (base.type.kind == GDScriptDataType::NATIVE) { + class_name = base.type.native_type; + } else { + class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type; + } + if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { + MethodBind *method = ClassDB::get_method(class_name, call->function_name); + if (_have_exact_arguments(method, arguments)) { + // Exact arguments, use ptrcall. + gen->write_call_ptrcall(result, base, method, arguments); + } else { + // Not exact arguments, but still can use method bind call. + gen->write_call_method_bind(result, base, method, arguments); + } + } else { + gen->write_call(result, base, call->function_name, arguments); + } + } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) { + gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments); + } else { + gen->write_call(result, base, call->function_name, arguments); + } + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + _set_error("Cannot call something that isn't a function.", call->callee); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } - arguments.push_back(ret); + } else { + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } + } + } - //push call bytecode - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_SELF_BASE); // basic type constructor + for (int i = 0; i < arguments.size(); i++) { + if (arguments[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + return result; + } break; + case GDScriptParser::Node::GET_NODE: { + const GDScriptParser::GetNodeNode *get_node = static_cast<const GDScriptParser::GetNodeNode *>(p_expression); - 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 + 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; + } + } - } break; - case GDScriptParser::OperatorNode::OP_CALL: { + Vector<GDScriptCodeGenerator::Address> args; + args.push_back(codegen.add_constant(NodePath(node_name))); - if (on->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - //construct a basic type - ERR_FAIL_COND_V(on->arguments.size() < 1, -1); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype())); - const GDScriptParser::TypeNode *tn = (const GDScriptParser::TypeNode *)on->arguments[0]; - int vtype = tn->vtype; + MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); + gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 1; i < on->arguments.size(); i++) { + return result; + } break; + case GDScriptParser::Node::PRELOAD: { + const GDScriptParser::PreloadNode *preload = static_cast<const GDScriptParser::PreloadNode *>(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); - } + // Add resource as constant. + return codegen.add_constant(preload->resource); + } break; + case GDScriptParser::Node::AWAIT: { + const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); + + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype())); + within_await = true; + GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await); + within_await = false; + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - //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 + gen->write_await(result, argument); - } else if (on->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - //built in function + if (argument.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - ERR_FAIL_COND_V(on->arguments.size() < 1, -1); + return result; + } break; + // Indexing operator. + case GDScriptParser::Node::SUBSCRIPT: { + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 1; i < on->arguments.size(); i++) { + bool named = subscript->is_attribute; + StringName name; + GDScriptCodeGenerator::Address index; + if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) { + index = p_index_addr; + } else if (subscript->is_attribute) { + if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { + GDScriptParser::IdentifierNode *identifier = subscript->attribute; + const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name); - int ret = _parse_expression(codegen, on->arguments[i], slevel); - if (ret < 0) - return ret; +#ifdef DEBUG_ENABLED + if (MI && MI->get().getter == codegen.function_name) { + String n = identifier->name; + _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } +#endif - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } + if (MI && MI->get().getter == "") { + // Remove result temp as we don't need it. + gen->pop_temporary(); + // Faster than indexing self (as if no self. had been used). + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->get().index, _gdtype_from_datatype(subscript->get_datatype())); + } + } - arguments.push_back(ret); - } + name = subscript->attribute->name; + named = true; + } else { + if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value.get_type() == Variant::STRING) { + // Also, somehow, named (speed up anyway). + name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value; + named = true; + } else { + // Regular indexing. + index = _parse_expression(codegen, r_error, subscript->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + } + } - 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]); + if (named) { + gen->write_get_named(result, name, base); + } else { + gen->write_get(result, index, base); + } - } else { - //regular function - ERR_FAIL_COND_V(on->arguments.size() < 2, -1); + if (index.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - const GDScriptParser::Node *instance = on->arguments[0]; + return result; + } break; + case GDScriptParser::Node::UNARY_OPERATOR: { + const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - if (instance->type == GDScriptParser::Node::TYPE_SELF) { - //room for optimization - } + GDScriptCodeGenerator::Address result = codegen.add_temporary(); - Vector<int> arguments; - int slevel = p_stack_level; + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - for (int i = 0; i < on->arguments.size(); i++) { + gen->write_unary_operator(result, unary->variant_op, operand); - int ret; + if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - 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) { + return result; + } + case GDScriptParser::Node::BINARY_OPERATOR: { + const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - 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); + GDScriptCodeGenerator::Address result = codegen.add_temporary(); - } else { + switch (binary->operation) { + case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { + // AND operator with early out on failure. + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + gen->write_and_left_operand(left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); + gen->write_and_right_operand(right_operand); - 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); - } + gen->write_end_and(result); - 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]); + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } } break; - case GDScriptParser::OperatorNode::OP_YIELD: { + case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: { + // OR operator with early out on success. + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + gen->write_or_left_operand(left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); + gen->write_or_right_operand(right_operand); - ERR_FAIL_COND_V(on->arguments.size() && on->arguments.size() != 2, -1); + gen->write_end_or(result); - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 0; i < on->arguments.size(); i++) { - - int ret = _parse_expression(codegen, on->arguments[i], slevel); - if (ret < 0) - return ret; - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - - //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 :) - } break; + case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: { + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand); - //indexing operator - case GDScriptParser::OperatorNode::OP_INDEX: - case GDScriptParser::OperatorNode::OP_INDEX_NAMED: { - - ERR_FAIL_COND_V(on->arguments.size() != 2, -1); - - int slevel = p_stack_level; - bool named = (on->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED); - - int from = _parse_expression(codegen, on->arguments[0], slevel); - if (from < 0) - return from; - - int index; - if (p_index_addr != 0) { - index = p_index_addr; - } else if (named) { - if (on->arguments[0]->type == GDScriptParser::Node::TYPE_SELF && codegen.script && codegen.function_node && !codegen.function_node->_static) { - - GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[1]); - const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(identifier->name); - -#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; - } -#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); - + 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) + Variant::Type type = GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name); + gen->write_type_test_builtin(result, operand, type); } else { - - if (on->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT && static_cast<const GDScriptParser::ConstantNode *>(on->arguments[1])->value.get_type() == Variant::STRING) { - //also, somehow, named (speed up anyway) - StringName name = static_cast<const GDScriptParser::ConstantNode *>(on->arguments[1])->value; - index = codegen.get_name_map_pos(name); - named = true; - - } else { - //regular indexing - if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } - - index = _parse_expression(codegen, on->arguments[1], slevel); - if (index < 0) - return index; + GDScriptCodeGenerator::Address type = _parse_expression(codegen, r_error, binary->right_operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_type_test(result, operand, type); + if (type.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } } - - 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) - - } break; - case GDScriptParser::OperatorNode::OP_AND: { - - // AND 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.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(res); - int jump_fail_pos2 = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); - codegen.opcodes.write[jump_fail_pos2] = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - } break; - case GDScriptParser::OperatorNode::OP_OR: { - - // OR operator with early out on success - - int res = _parse_expression(codegen, on->arguments[0], 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) - return res; - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); - codegen.opcodes.push_back(res); - int jump_success_pos2 = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes.write[jump_success_pos] = codegen.opcodes.size(); - codegen.opcodes.write[jump_success_pos2] = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + default: { + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); - } break; - // ternary operators - case GDScriptParser::OperatorNode::OP_TERNARY_IF: { + gen->write_binary_operator(result, binary->variant_op, left_operand, right_operand); - // x IF a ELSE y operator with early out on failure + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + } + return result; + } break; + 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); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype())); - 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); + gen->write_start_ternary(result); - res = _parse_expression(codegen, on->arguments[1], p_stack_level); - if (res < 0) - return res; + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, r_error, ternary->condition); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_condition(condition); - 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); + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); - res = _parse_expression(codegen, on->arguments[2], p_stack_level); - if (res < 0) - return res; + GDScriptCodeGenerator::Address true_expr = _parse_expression(codegen, r_error, ternary->true_expr); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_true_expr(true_expr); + if (true_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(res); + GDScriptCodeGenerator::Address false_expr = _parse_expression(codegen, r_error, ternary->false_expr); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_false_expr(false_expr); + if (false_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size(); + gen->write_end_ternary(); - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + return result; + } break; + case GDScriptParser::Node::ASSIGNMENT: { + const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression); - } 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; - } break; - case GDScriptParser::OperatorNode::OP_EQUAL: { - if (!_create_binary_operator(codegen, on, 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; - } break; - case GDScriptParser::OperatorNode::OP_LESS: { - if (!_create_binary_operator(codegen, on, 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; - } break; - case GDScriptParser::OperatorNode::OP_GREATER: { - if (!_create_binary_operator(codegen, on, 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; - } break; - case GDScriptParser::OperatorNode::OP_ADD: { - if (!_create_binary_operator(codegen, on, 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; - } break; - case GDScriptParser::OperatorNode::OP_MUL: { - if (!_create_binary_operator(codegen, on, 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; - } break; - case GDScriptParser::OperatorNode::OP_MOD: { - if (!_create_binary_operator(codegen, on, 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; - } break; - case GDScriptParser::OperatorNode::OP_BIT_OR: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_OR, p_stack_level)) return -1; - } break; - case GDScriptParser::OperatorNode::OP_BIT_XOR: { - if (!_create_binary_operator(codegen, on, Variant::OP_BIT_XOR, p_stack_level)) 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; - } break; - case GDScriptParser::OperatorNode::OP_SHIFT_RIGHT: { - if (!_create_binary_operator(codegen, on, 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! + if (assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { + // SET (chained) MODE! + const GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(assignment->assignee); #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; + if (subscript->is_attribute && subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { + const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name); + if (MI && MI->get().setter == codegen.function_name) { + String n = subscript->attribute->name; + _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } + } +#endif + /* Find chain of sets */ + + StringName assign_property; + + List<const GDScriptParser::SubscriptNode *> chain; + + { + // Create get/set chain. + const GDScriptParser::SubscriptNode *n = subscript; + while (true) { + chain.push_back(n); + if (n->base->type != GDScriptParser::Node::SUBSCRIPT) { + // Check for a built-in property. + 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; } -#endif - - int slevel = p_stack_level; - - GDScriptParser::OperatorNode *op = static_cast<GDScriptParser::OperatorNode *>(on->arguments[0]); - - /* Find chain of sets */ + n = static_cast<const GDScriptParser::SubscriptNode *>(n->base); + } + } - StringName assign_property; + /* Chain of gets */ - List<GDScriptParser::OperatorNode *> chain; + // Get at (potential) root stack pos, so it can be returned. + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - { - //create get/set chain - GDScriptParser::OperatorNode *n = op; - while (true) { + GDScriptCodeGenerator::Address prev_base = base; - chain.push_back(n); - if (n->arguments[0]->type != GDScriptParser::Node::TYPE_OPERATOR) { + struct ChainInfo { + bool is_named = false; + GDScriptCodeGenerator::Address base; + GDScriptCodeGenerator::Address key; + StringName name; + }; - //check for a built-in property - if (n->arguments[0]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { + List<ChainInfo> set_chain; - 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; - } + for (List<const GDScriptParser::SubscriptNode *>::Element *E = chain.back(); E; E = E->prev()) { + if (E == chain.front()) { + // Skip the main subscript, since we'll assign to that. + break; + } + const GDScriptParser::SubscriptNode *subscript_elem = E->get(); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype())); + GDScriptCodeGenerator::Address key; + StringName name; + + if (subscript_elem->is_attribute) { + name = subscript_elem->attribute->name; + gen->write_get_named(value, name, prev_base); + } else { + key = _parse_expression(codegen, r_error, subscript_elem->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); } + gen->write_get(value, key, prev_base); + } - /* Chain of gets */ + // Store base and key for setting it back later. + set_chain.push_front({ subscript_elem->is_attribute, prev_base, key, name }); // Push to front to invert the list. + prev_base = value; + } - //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; + // Get value to assign. + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + // Get the key if needed. + GDScriptCodeGenerator::Address key; + StringName name; + if (subscript->is_attribute) { + name = subscript->attribute->name; + } else { + key = _parse_expression(codegen, r_error, subscript->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + } - if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + // Perform operator if any. + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address value = codegen.add_temporary(); + if (subscript->is_attribute) { + gen->write_get_named(value, name, prev_base); + } else { + gen->write_get(value, key, prev_base); + } + gen->write_binary_operator(value, assignment->variant_op, value, assigned); + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + assigned = value; + } - Vector<int> setchain; + // Perform assignment. + if (subscript->is_attribute) { + gen->write_set_named(prev_base, name, assigned); + } else { + gen->write_set(prev_base, key, assigned); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - if (assign_property != StringName()) { + assigned = prev_base; - // 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); + // Set back the values into their bases. + for (List<ChainInfo>::Element *E = set_chain.front(); E; E = E->next()) { + const ChainInfo &info = E->get(); + if (!info.is_named) { + gen->write_set(info.base, info.key, assigned); + if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } + } else { + gen->write_set_named(info.base, info.name, assigned); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + assigned = info.base; + } - 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; + // If this is a local member, also assign to it. + // This allow things like: position.x += 2.0 + if (assign_property != StringName()) { + gen->write_set_member(assigned, assign_property); + } - if (named) { + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name)) { + // Assignment to member property. + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + GDScriptCodeGenerator::Address assign_temp = assigned; - key_idx = codegen.get_name_map_pos(static_cast<const GDScriptParser::IdentifierNode *>(E->get()->arguments[1])->name); - //printf("named key %x\n",key_idx); + StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - } else { - - if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) { - slevel++; - codegen.alloc_stack(slevel); - } + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address member = codegen.add_temporary(); + gen->write_get_member(member, name); + gen->write_binary_operator(assigned, assignment->variant_op, member, assigned); + gen->pop_temporary(); + } - GDScriptParser::Node *key = E->get()->arguments[1]; - key_idx = _parse_expression(codegen, key, slevel); - //printf("expr key %x\n",key_idx); + gen->write_set_member(assigned, name); - //stack was raised here if retval was stack but.. - } + if (assign_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + // Regular assignment. + GDScriptCodeGenerator::Address target; + + bool has_setter = false; + bool is_in_setter = false; + StringName setter_function; + if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; + if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + setter_function = codegen.script->member_indices[var_name].setter; + if (setter_function != StringName()) { + has_setter = true; + is_in_setter = setter_function == codegen.function_name; + target.mode = GDScriptCodeGenerator::Address::MEMBER; + target.address = codegen.script->member_indices[var_name].index; + } + } + } - if (key_idx < 0) //error - return key_idx; + if (has_setter) { + if (!is_in_setter) { + // Store stack slot for the temp value. + target = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype())); + } + } else { + target = _parse_expression(codegen, r_error, assignment->assignee); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + } - codegen.opcodes.push_back(named ? 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; + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + GDScriptCodeGenerator::Address op_result; + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - codegen.opcodes.push_back(dst_pos); + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + // Perform operation. + op_result = codegen.add_temporary(); + gen->write_binary_operator(op_result, assignment->variant_op, target, assigned); + } else { + op_result = assigned; + assigned = GDScriptCodeGenerator::Address(); + } - //add in reverse order, since it will be reverted + GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); - 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); + if (has_setter && !is_in_setter) { + // Call setter. + Vector<GDScriptCodeGenerator::Address> args; + args.push_back(op_result); + gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args); + } else { + // Just assign. + gen->write_assign(target, op_result); + } - prev_pos = dst_pos; - } + if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (target.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + return GDScriptCodeGenerator::Address(); // Assignment does not return a value. + } break; + default: { + ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code. + } break; + } +} - setchain.invert(); +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested) { + switch (p_pattern->pattern_type) { + case GDScriptParser::PatternNode::PT_LITERAL: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } - int set_index; - bool named = false; + // Get literal type into constant map. + GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant((int)p_pattern->literal->value.get_type()); - if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) { + // Equality is always a boolean. + GDScriptDataType equality_type; + equality_type.has_type = true; + equality_type.kind = GDScriptDataType::BUILTIN; + equality_type.builtin_type = Variant::BOOL; - set_index = codegen.get_name_map_pos(static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name); - named = true; - } else { + // Check type equality. + GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + codegen.generator->write_and_left_operand(type_equality_addr); - set_index = _parse_expression(codegen, op->arguments[1], slevel + 1); - named = false; - } - - if (set_index < 0) //error - return set_index; + // Get literal. + GDScriptCodeGenerator::Address literal_addr = _parse_expression(codegen, r_error, p_pattern->literal); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + // Check value equality. + GDScriptCodeGenerator::Address equality_addr = codegen.add_temporary(equality_type); + codegen.generator->write_binary_operator(equality_addr, Variant::OP_EQUAL, p_value_addr, literal_addr); + codegen.generator->write_and_right_operand(equality_addr); - int set_value = _parse_assign_right_expression(codegen, on, slevel + 1, named ? 0 : set_index); - if (set_value < 0) //error - return set_value; + // AND both together (reuse temporary location). + codegen.generator->write_end_and(type_equality_addr); - 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); + codegen.generator->pop_temporary(); // Remove equality_addr from stack. - for (int i = 0; i < setchain.size(); i++) { + if (literal_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } - codegen.opcodes.push_back(setchain[i]); - } + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(type_equality_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(type_equality_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, type_equality_addr); + } + codegen.generator->pop_temporary(); // Remove type_equality_addr. - return retval; + return p_previous_test; + } break; + case GDScriptParser::PatternNode::PT_EXPRESSION: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Create the result temps first since it's the last to go away. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary(); + + // Evaluate expression. + GDScriptCodeGenerator::Address expr_addr; + expr_addr = _parse_expression(codegen, r_error, p_pattern->expression); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - } 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 + // Evaluate expression type. + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(expr_addr); + codegen.generator->write_call_utility(result_addr, "typeof", typeof_args); - int slevel = p_stack_level; + // Check type equality. + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr); + codegen.generator->write_and_left_operand(result_addr); - int src_address = _parse_assign_right_expression(codegen, on, slevel); - if (src_address < 0) - return -1; + // Check value equality. + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_value_addr, expr_addr); + codegen.generator->write_and_right_operand(equality_test_addr); - StringName name = static_cast<GDScriptParser::IdentifierNode *>(on->arguments[0])->name; + // AND both type and value equality. + codegen.generator->write_end_and(result_addr); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER); - codegen.opcodes.push_back(codegen.get_name_map_pos(name)); - codegen.opcodes.push_back(src_address); + // We don't need the expression temporary anymore. + if (expr_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + codegen.generator->pop_temporary(); // Remove type equality temporary. + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. - return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; - } else { + return p_previous_test; + } break; + case GDScriptParser::PatternNode::PT_ARRAY: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Get array type into constant map. + GDScriptCodeGenerator::Address array_type_addr = codegen.add_constant((int)Variant::ARRAY); + + // Equality is always a boolean. + GDScriptDataType temp_type; + temp_type.has_type = true; + temp_type.kind = GDScriptDataType::BUILTIN; + temp_type.builtin_type = Variant::BOOL; + + // Check type equality. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, array_type_addr); + codegen.generator->write_and_left_operand(result_addr); + + // Store pattern length in constant map. + GDScriptCodeGenerator::Address array_length_addr = codegen.add_constant(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size()); + + // Get value length. + temp_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); + Vector<GDScriptCodeGenerator::Address> len_args; + len_args.push_back(p_value_addr); + codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), len_args); + + // Test length compatibility. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, array_length_addr); + codegen.generator->write_and_right_operand(length_compat_addr); + + // AND type and length check. + codegen.generator->write_end_and(result_addr); + + // Remove length temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + // Create temporaries outside the loop so they can be reused. + GDScriptCodeGenerator::Address element_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address test_addr = p_previous_test; + + // Evaluate element by element. + for (int i = 0; i < p_pattern->array.size(); i++) { + if (p_pattern->array[i]->pattern_type == GDScriptParser::PatternNode::PT_REST) { + // Don't want to access an extra element of the user array. + break; + } - //REGULAR ASSIGNMENT MODE!! + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_addr); - int slevel = p_stack_level; + // Add index to constant map. + GDScriptCodeGenerator::Address index_addr = codegen.add_constant(i); - 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; + // Get the actual element from the user-sent array. + codegen.generator->write_get(element_addr, index_addr, p_value_addr); - if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + // Also get type of element. + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(element_addr); + codegen.generator->write_call_utility(element_type_addr, "typeof", typeof_args); - int src_address_b = _parse_assign_right_expression(codegen, on, slevel); - if (src_address_b < 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 - } - } break; - case GDScriptParser::OperatorNode::OP_IS: { - - ERR_FAIL_COND_V(on->arguments.size() != 2, false); + // Try the pattern inside the element. + test_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, p_previous_test, false, true); + if (r_error != OK) { + return GDScriptCodeGenerator::Address(); + } - int slevel = p_stack_level; + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); + } + // Remove element temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); - int src_address_a = _parse_expression(codegen, on->arguments[0], slevel); - if (src_address_a < 0) - return -1; + return test_addr; + } break; + case GDScriptParser::PatternNode::PT_DICTIONARY: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Get dictionary type into constant map. + GDScriptCodeGenerator::Address dict_type_addr = codegen.add_constant((int)Variant::DICTIONARY); + + // Equality is always a boolean. + GDScriptDataType temp_type; + temp_type.has_type = true; + temp_type.kind = GDScriptDataType::BUILTIN; + temp_type.builtin_type = Variant::BOOL; + + // Check type equality. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, dict_type_addr); + codegen.generator->write_and_left_operand(result_addr); + + // Store pattern length in constant map. + GDScriptCodeGenerator::Address dict_length_addr = codegen.add_constant(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); + + // Get user's dictionary length. + temp_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); + Vector<GDScriptCodeGenerator::Address> func_args; + func_args.push_back(p_value_addr); + codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), func_args); + + // Test length compatibility. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, dict_length_addr); + codegen.generator->write_and_right_operand(length_compat_addr); + + // AND type and length check. + codegen.generator->write_end_and(result_addr); + + // Remove length temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + // Create temporaries outside the loop so they can be reused. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address test_result = codegen.add_temporary(temp_type); + GDScriptCodeGenerator::Address element_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address test_addr = p_previous_test; + + // Evaluate element by element. + for (int i = 0; i < p_pattern->dictionary.size(); i++) { + const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i]; + if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { + // Ignore rest pattern. + break; + } - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) - slevel++; //uses stack for return, increase stack + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_addr); - int src_address_b = _parse_expression(codegen, on->arguments[1], slevel); - if (src_address_b < 0) - return -1; + // Get the pattern key. + GDScriptCodeGenerator::Address pattern_key_addr = _parse_expression(codegen, r_error, element.key); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } - 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 if pattern key exists in user's dictionary. This will be AND-ed with next result. + func_args.clear(); + func_args.push_back(pattern_key_addr); + codegen.generator->write_call(test_result, p_value_addr, "has", func_args); - } 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); + if (element.value_pattern != nullptr) { + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_result); - int slevel = p_stack_level; + // Get actual value from user dictionary. + codegen.generator->write_get(element_addr, pattern_key_addr, p_value_addr); - int src_address_a = _parse_expression(codegen, on->arguments[0], slevel); - if (src_address_a < 0) - return -1; + // Also get type of value. + func_args.clear(); + func_args.push_back(element_addr); + codegen.generator->write_call_utility(element_type_addr, "typeof", func_args); - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) - slevel++; //uses stack for return, increase stack + // Try the pattern inside the value. + test_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, test_addr, false, true); + if (r_error != OK) { + return GDScriptCodeGenerator::Address(); + } + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); + } - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(on->arguments[1]); + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); - 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: { + // Remove pattern key temporary. + if (pattern_key_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } - ERR_FAIL_V_MSG(0, "Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression."); //unreachable code + // Remove element temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); - } break; + return test_addr; + } break; + case GDScriptParser::PatternNode::PT_REST: + // Do nothing. + return p_previous_test; + break; + case GDScriptParser::PatternNode::PT_BIND: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); } + // Get the bind address. + GDScriptCodeGenerator::Address bind = codegen.locals[p_pattern->bind->name]; - 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: { + // Assign value to bound variable. + codegen.generator->write_assign(bind, p_value_addr); + } + [[fallthrough]]; // Act like matching anything too. + case GDScriptParser::PatternNode::PT_WILDCARD: + // If this is a fall through we don't want to do this again. + if (p_pattern->pattern_type != GDScriptParser::PatternNode::PT_BIND) { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + } + // This matches anything so just do the same as `if(true)`. + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the operator with the `true` constant so it works as always matching. + GDScriptCodeGenerator::Address constant = codegen.add_constant(true); + codegen.generator->write_and_right_operand(constant); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the operator with the `true` constant so it works as always matching. + GDScriptCodeGenerator::Address constant = codegen.add_constant(true); + codegen.generator->write_or_right_operand(constant); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign_true(p_previous_test); + } + return p_previous_test; + } + ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern."); +} - ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code - } break; +void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) { + for (int i = 0; i < p_block->locals.size(); i++) { + if (p_block->locals[i].type == GDScriptParser::SuiteNode::Local::PARAMETER || p_block->locals[i].type == GDScriptParser::SuiteNode::Local::FOR_VARIABLE) { + // Parameters are added directly from function and loop variables are declared explicitly. + continue; + } + codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype())); } } -Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::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, bool p_add_locals) { + Error error = OK; + GDScriptCodeGenerator *gen = codegen.generator; - codegen.push_stack_identifiers(); - int new_identifiers = 0; - codegen.current_line = p_block->line; + codegen.start_block(); - for (int i = 0; i < p_block->statements.size(); i++) { + if (p_add_locals) { + _add_locals_in_block(codegen, p_block); + } + for (int i = 0; i < p_block->statements.size(); i++) { const GDScriptParser::Node *s = p_block->statements[i]; - 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. + gen->write_newline(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; + gen->start_match(); + codegen.start_block(); - GDScriptParser::IdentifierNode *id = memnew(GDScriptParser::IdentifierNode); - id->name = "#match_value"; + // Evaluate the match expression. + GDScriptCodeGenerator::Address value_local = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); + GDScriptCodeGenerator::Address value = _parse_expression(codegen, error, match->test); + if (error) { + return error; + } - // 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++; + // Assign to local. + // TODO: This can be improved by passing the target to parse_expression(). + gen->write_assign(value_local, value); - 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); + if (value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } - int ret = _parse_expression(codegen, op, p_stack_level); - if (ret < 0) { - memdelete(id); - memdelete(op); - return ERR_PARSE_ERROR; - } + // Then, let's save the type of the value in the stack too, so we can reuse for later comparisons. + GDScriptDataType typeof_type; + typeof_type.has_type = true; + typeof_type.kind = GDScriptDataType::BUILTIN; + typeof_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address type = codegen.add_local("@match_type", typeof_type); + + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(value); + gen->write_call_utility(type, "typeof", typeof_args); + + // Now we can actually start testing. + // For each branch. + for (int j = 0; j < match->branches.size(); j++) { + if (j > 0) { + // Use `else` to not check the next branch after matching. + gen->write_else(); + } - // 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; - } + const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - 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; - } + gen->start_match_branch(); // Need so lower level code can patch 'continue' jumps. + codegen.start_block(); // Create an extra block around for binds. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(break_addr); + // Add locals in block before patterns, so temporaries don't use the stack address for binds. + _add_locals_in_block(codegen, branch->block); - codegen.opcodes.write[continue_addr + 1] = codegen.opcodes.size(); +#ifdef DEBUG_ENABLED + // Add a newline before each branch, since the debugger needs those. + gen->write_newline(branch->start_line); +#endif + // For each pattern in branch. + GDScriptCodeGenerator::Address pattern_result = codegen.add_temporary(); + for (int k = 0; k < branch->patterns.size(); k++) { + pattern_result = _parse_match_pattern(codegen, error, branch->patterns[k], value, type, pattern_result, k == 0, false); + if (error != OK) { + return error; } + } - codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); - - memdelete(id); - memdelete(op); - - } break; - - case GDScriptParser::ControlFlowNode::CF_IF: { + // Check if pattern did match. + gen->write_if(pattern_result); - int ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false); - if (ret2 < 0) - return ERR_PARSE_ERROR; + // Remove the result from stack. + gen->pop_temporary(); - 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 + // Parse the branch block. + error = _parse_block(codegen, branch->block, false); // Don't add locals again. + if (error) { + return error; + } - Error err = _parse_block(codegen, cf->body, p_stack_level, p_break_addr, p_continue_addr); - if (err) - return err; + codegen.end_block(); // Get out of extra block. + } - if (cf->body_else) { + // End all nested `if`s. + for (int j = 0; j < match->branches.size(); j++) { + gen->write_endif(); + } - 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(); + gen->end_match(); + } break; + case GDScriptParser::Node::IF: { + const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s); + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, if_n->condition); + if (error) { + return error; + } - Error err2 = _parse_block(codegen, cf->body_else, p_stack_level, p_break_addr, p_continue_addr); - if (err2) - return err2; + gen->write_if(condition); - codegen.opcodes.write[end_addr] = codegen.opcodes.size(); - } else { - //end without else - codegen.opcodes.write[else_addr] = codegen.opcodes.size(); - } + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } - } break; - case GDScriptParser::ControlFlowNode::CF_FOR: { - - int slevel = p_stack_level; - int iter_stack_pos = slevel; - int iterator_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - int counter_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - int container_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.alloc_stack(slevel); - - codegen.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; + error = _parse_block(codegen, if_n->true_block); + if (error) { + return error; + } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(continue_pos); - codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size(); + if (if_n->false_block) { + gen->write_else(); - codegen.pop_stack_identifiers(); + error = _parse_block(codegen, if_n->false_block); + if (error) { + return error; + } + } - } break; - case GDScriptParser::ControlFlowNode::CF_WHILE: { + gen->write_endif(); + } break; + case GDScriptParser::Node::FOR: { + const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(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(); + codegen.start_block(); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype())); - 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); + gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype())); - codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); + GDScriptCodeGenerator::Address list = _parse_expression(codegen, error, for_n->list); + if (error) { + return error; + } - } break; - case GDScriptParser::ControlFlowNode::CF_BREAK: { + gen->write_for_assignment(iterator, list); - if (p_break_addr < 0) { + if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } - _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); + gen->write_for(); - } break; - case GDScriptParser::ControlFlowNode::CF_CONTINUE: { + error = _parse_block(codegen, for_n->loop); + if (error) { + return error; + } - if (p_continue_addr < 0) { + gen->write_endfor(); - _set_error("'continue' not within loop", cf); - return ERR_COMPILATION_FAILED; - } + codegen.end_block(); + } break; + case GDScriptParser::Node::WHILE: { + const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(p_continue_addr); + gen->start_while_condition(); - } break; - case GDScriptParser::ControlFlowNode::CF_RETURN: { + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, while_n->condition); + if (error) { + return error; + } - int ret2; + gen->write_while(condition); - if (cf->arguments.size()) { + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } - ret2 = _parse_expression(codegen, cf->arguments[0], p_stack_level, false); - if (ret2 < 0) - return ERR_PARSE_ERROR; + error = _parse_block(codegen, while_n->loop); + if (error) { + return error; + } - } else { + gen->write_endwhile(); + } break; + case GDScriptParser::Node::BREAK: { + gen->write_break(); + } break; + case GDScriptParser::Node::CONTINUE: { + const GDScriptParser::ContinueNode *cont = static_cast<const GDScriptParser::ContinueNode *>(s); + if (cont->is_for_match) { + gen->write_continue_match(); + } else { + gen->write_continue(); + } + } break; + case GDScriptParser::Node::RETURN: { + const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s); - ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; - } + GDScriptCodeGenerator::Address return_value; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN); - codegen.opcodes.push_back(ret2); + if (return_n->return_value != nullptr) { + return_value = _parse_expression(codegen, error, return_n->return_value); + if (error) { + return error; + } + } - } break; + gen->write_return(return_value); + if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } } 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) - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, as->condition); + if (error) { + return error; + } + + GDScriptCodeGenerator::Address message; - int message_ret = 0; if (as->message) { - message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false); - if (message_ret < 0) - return ERR_PARSE_ERROR; + message = _parse_expression(codegen, error, as->message); + if (error) { + return error; + } } + gen->write_assert(condition, message); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT); - codegen.opcodes.push_back(ret2); - codegen.opcodes.push_back(message_ret); + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + if (message.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } #endif } break; - case GDScriptParser::Node::TYPE_BREAKPOINT: { + case GDScriptParser::Node::BREAKPOINT: { #ifdef DEBUG_ENABLED - // try subblocks - codegen.opcodes.push_back(GDScriptFunction::OPCODE_BREAKPOINT); + gen->write_breakpoint(); #endif } break; - case GDScriptParser::Node::TYPE_LOCAL_VAR: { - - const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(s); - - // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. - // - //if (_is_class_member_property(codegen, lv->name)) { - // _set_error("Name for local variable '" + String(lv->name) + "' can't shadow class property of the same name.", lv); - // return ERR_ALREADY_EXISTS; - //} - - codegen.add_stack_identifier(lv->name, p_stack_level++); - codegen.alloc_stack(p_stack_level); - new_identifiers++; + case GDScriptParser::Node::VARIABLE: { + const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); + // Should be already in stack when the block began. + GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; + + if (lv->initializer != nullptr) { + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, lv->initializer); + if (error) { + return error; + } + gen->write_assign(local, src_address); + if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } + } 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.add_local_constant(lc->identifier->name, 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; + // Expression. + if (s->is_expression()) { + GDScriptCodeGenerator::Address expr = _parse_expression(codegen, error, static_cast<const GDScriptParser::ExpressionNode *>(s), true); + if (error) { + return error; + } + if (expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } else { + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); // Unreachable code. + } } break; } } - codegen.pop_stack_identifiers(); + + codegen.end_block(); return OK; } Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { - - Vector<int> bytecode; + Error error = OK; CodeGen codegen; + codegen.generator = memnew(GDScriptByteCodeGenerator); codegen.class_node = p_class; codegen.script = p_script; codegen.function_node = p_func; - codegen.stack_max = 0; - codegen.current_line = 0; - codegen.call_max = 0; - codegen.debug_stack = EngineDebugger::is_active(); - Vector<StringName> argnames; - int stack_level = 0; + StringName func_name; + bool is_static = false; + MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + GDScriptDataType return_type; + return_type.has_type = true; + return_type.kind = GDScriptDataType::BUILTIN; + return_type.builtin_type = Variant::NIL; if (p_func) { - for (int i = 0; i < p_func->arguments.size(); i++) { - // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. - // - //if (_is_class_member_property(p_script, p_func->arguments[i])) { - // _set_error("Name for argument '" + String(p_func->arguments[i]) + "' can't shadow class property of the same name.", p_func); - // return ERR_ALREADY_EXISTS; - //} - - codegen.add_stack_identifier(p_func->arguments[i], i); -#ifdef TOOLS_ENABLED - argnames.push_back(p_func->arguments[i]); -#endif + func_name = p_func->identifier->name; + is_static = p_func->is_static; + rpc_mode = p_func->rpc_mode; + return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); + } else { + if (p_for_ready) { + func_name = "_ready"; + } else { + func_name = "@implicit_new"; } - stack_level = p_func->arguments.size(); } - codegen.alloc_stack(stack_level); - - /* Parse initializer -if applies- */ - - bool is_initializer = !p_for_ready && !p_func; - - 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()) { + codegen.function_name = func_name; + codegen.generator->write_start(p_script, func_name, is_static, rpc_mode, return_type); - //call implicit parent constructor - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_SELF_BASE); - codegen.opcodes.push_back(codegen.get_name_map_pos("_init")); - codegen.opcodes.push_back(0); - codegen.opcodes.push_back((GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | 0); - } - Error err = _parse_block(codegen, p_class->initializer, stack_level); - if (err) - return err; - is_initializer = true; - } + int optional_parameters = 0; - 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_func) { + for (int i = 0; i < p_func->parameters.size(); i++) { + const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; + GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script); + uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type); + codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type); + + if (p_func->parameters[i]->default_value != nullptr) { + optional_parameters++; + } } } - /* Parse default argument code -if applies- */ - - Vector<int> defarg_addr; - StringName func_name; + // Parse initializer if applies. + bool is_implicit_initializer = !p_for_ready && !p_func; + bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; + bool is_for_ready = p_for_ready || (p_func && String(p_func->identifier->name) == "_ready"); - if (p_func) { + if (is_implicit_initializer || is_for_ready) { + // Initialize class fields. + for (int i = 0; i < p_class->members.size(); i++) { + if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { + continue; + } + const GDScriptParser::VariableNode *field = p_class->members[i].variable; + if (field->onready != is_for_ready) { + // Only initialize in _ready. + continue; + } - if (p_func->default_values.size()) { + if (field->initializer) { + // Emit proper line change. + codegen.generator->write_newline(field->initializer->start_line); - 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++) { + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true); + if (error) { + memdelete(codegen.generator); + return error; + } + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); - _parse_expression(codegen, p_func->default_values[i], stack_level, true); - defarg_addr.push_back(codegen.opcodes.size()); + codegen.generator->write_assign(dst_address, src_address); + if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } } + } + } - defarg_addr.invert(); + // Parse default argument code if applies. + if (p_func) { + if (optional_parameters > 0) { + codegen.generator->start_parameters(); + for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { + const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; + GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, error, parameter->default_value, true); + if (error) { + memdelete(codegen.generator); + return error; + } + GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; + codegen.generator->write_assign_default_parameter(dst_addr, src_addr); + if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } + codegen.generator->end_parameters(); } - Error err = _parse_block(codegen, p_func->body, stack_level); - if (err) + Error err = _parse_block(codegen, p_func->body); + if (err) { + memdelete(codegen.generator); return err; - - func_name = p_func->name; - } else { - if (p_for_ready) - func_name = "_ready"; - else - func_name = "_init"; + } } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); +#ifdef DEBUG_ENABLED + if (EngineDebugger::is_active()) { + String signature; + // Path. + if (p_script->get_path() != String()) { + signature += p_script->get_path(); + } + // Location. + if (p_func) { + signature += "::" + itos(p_func->body->start_line); + } else { + signature += "::0"; + } - /* - if (String(p_func->name)=="") { //initializer func - gdfunc = &p_script->initializer; - */ - //} else { //regular func - p_script->member_functions[func_name] = memnew(GDScriptFunction); - GDScriptFunction *gdfunc = p_script->member_functions[func_name]; - //} + // Function and class. - if (p_func) { - gdfunc->_static = p_func->_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]); + if (p_class->identifier) { + signature += "::" + String(p_class->identifier->name) + "." + String(func_name); + } else { + signature += "::" + String(func_name); } - gdfunc->return_type = _gdtype_from_datatype(p_func->return_type); - } else { - gdfunc->_static = false; - gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; - gdfunc->return_type = GDScriptDataType(); - gdfunc->return_type.has_type = true; - gdfunc->return_type.kind = GDScriptDataType::BUILTIN; - gdfunc->return_type.builtin_type = Variant::NIL; + + codegen.generator->set_signature(signature); } +#endif + if (p_func) { + codegen.generator->set_initial_line(p_func->start_line); #ifdef TOOLS_ENABLED - gdfunc->arg_names = argnames; + p_script->member_lines[func_name] = p_func->start_line; + p_script->doc_functions[func_name] = p_func->doc_description; #endif - //constants - if (codegen.constant_map.size()) { - gdfunc->_constant_count = codegen.constant_map.size(); - gdfunc->constants.resize(codegen.constant_map.size()); - gdfunc->_constants_ptr = gdfunc->constants.ptrw(); - const Variant *K = nullptr; - while ((K = codegen.constant_map.next(K))) { - int idx = codegen.constant_map[*K]; - gdfunc->constants.write[idx] = *K; - } } else { - - gdfunc->_constants_ptr = nullptr; - gdfunc->_constant_count = 0; + codegen.generator->set_initial_line(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()) { + GDScriptFunction *gd_function = codegen.generator->write_end(); - 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; + if (is_initializer) { + p_script->initializer = gd_function; + } else if (is_implicit_initializer) { + p_script->implicit_initializer = gd_function; } -#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]; + if (p_func) { + // if no return statement -> return type is void not unresolved Variant + if (p_func->body->has_return) { + gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype()); + } else { + gd_function->return_type = GDScriptDataType(); + gd_function->return_type.has_type = true; + gd_function->return_type.kind = GDScriptDataType::BUILTIN; + gd_function->return_type.builtin_type = Variant::NIL; } - gdfunc->_named_globals_count = gdfunc->named_globals.size(); - } +#ifdef TOOLS_ENABLED + gd_function->default_arg_values = p_func->default_arg_values; #endif + } - if (codegen.opcodes.size()) { + p_script->member_functions[func_name] = gd_function; - gdfunc->code = codegen.opcodes; - gdfunc->_code_ptr = &gdfunc->code[0]; - gdfunc->_code_size = codegen.opcodes.size(); + memdelete(codegen.generator); + + return OK; +} + +Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { + Error error = OK; + CodeGen codegen; + codegen.generator = memnew(GDScriptByteCodeGenerator); + + codegen.class_node = p_class; + codegen.script = p_script; + StringName func_name; + + if (p_is_setter) { + func_name = "@" + p_variable->identifier->name + "_setter"; } else { + func_name = "@" + p_variable->identifier->name + "_getter"; + } - gdfunc->_code_ptr = nullptr; - gdfunc->_code_size = 0; + GDScriptDataType return_type; + if (p_is_setter) { + return_type.has_type = true; + return_type.kind = GDScriptDataType::BUILTIN; + return_type.builtin_type = Variant::NIL; + } else { + return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } - if (defarg_addr.size()) { + codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type); - gdfunc->default_arguments = defarg_addr; - gdfunc->_default_arg_count = defarg_addr.size() - 1; - gdfunc->_default_arg_ptr = &gdfunc->default_arguments[0]; - } else { - gdfunc->_default_arg_count = 0; - gdfunc->_default_arg_ptr = nullptr; + if (p_is_setter) { + uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); + codegen.parameters[p_variable->setter_parameter->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, _gdtype_from_datatype(p_variable->get_datatype())); } - gdfunc->_argument_count = p_func ? p_func->arguments.size() : 0; - gdfunc->_stack_size = codegen.stack_max; - gdfunc->_call_size = codegen.call_max; - gdfunc->name = func_name; + error = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter); + if (error) { + memdelete(codegen.generator); + return error; + } + + GDScriptFunction *gd_function = codegen.generator->write_end(); + + p_script->member_functions[func_name] = gd_function; + #ifdef DEBUG_ENABLED 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); - } else { - signature += "::0"; } + //loc + signature += "::" + itos(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); //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); } - gdfunc->profile.signature = signature; + codegen.generator->set_signature(signature); } #endif - gdfunc->_script = p_script; - gdfunc->source = source; - -#ifdef DEBUG_ENABLED + codegen.generator->set_initial_line(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); - { - gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); - gdfunc->_func_cname = gdfunc->func_cname.get_data(); - } - -#endif - if (p_func) { - gdfunc->_initial_line = p_func->line; #ifdef TOOLS_ENABLED - - p_script->member_lines[func_name] = p_func->line; + p_script->member_lines[func_name] = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; #endif - } else { - gdfunc->_initial_line = 0; - } - - if (codegen.debug_stack) - gdfunc->stack_debug = codegen.stack_debug; - - if (is_initializer) - p_script->initializer = gdfunc; + memdelete(codegen.generator); 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; } } } +#ifdef TOOLS_ENABLED + p_script->doc_functions.clear(); + p_script->doc_variables.clear(); + p_script->doc_constants.clear(); + p_script->doc_enums.clear(); + p_script->doc_signals.clear(); + p_script->doc_tutorials.clear(); + + p_script->doc_brief_description = p_class->doc_brief_description; + p_script->doc_description = p_class->doc_description; + for (int i = 0; i < p_class->doc_tutorials.size(); i++) { + DocData::TutorialDoc td; + td.title = p_class->doc_tutorials[i].first; + td.link = p_class->doc_tutorials[i].second; + p_script->doc_tutorials.append(td); + } +#endif + p_script->native = Ref<GDScriptNativeClass>(); p_script->base = Ref<GDScript>(); p_script->_base = nullptr; @@ -1850,8 +2055,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; @@ -1865,23 +2070,38 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->native = native; } break; case GDScriptDataType::GDSCRIPT: { - Ref<GDScript> base = base_type.script_type; + Ref<GDScript> base = Ref<GDScript>(base_type.script_type); p_script->base = base; p_script->_base = base.ptr(); - p_script->member_indices = base->member_indices; - if (p_class->base_type.kind == GDScriptParser::DataType::CLASS) { - 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); - return ERR_PARSE_ERROR; + if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { + if (p_class->base_type.script_path == main_script->path) { + if (!parsed_classes.has(p_script->_base)) { + if (parsing_classes.has(p_script->_base)) { + String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; + _set_error("Cyclic class reference for '" + class_name + "'.", p_class); + return ERR_PARSE_ERROR; + } + Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); + if (err) { + return err; + } } - Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); + } else { + Error err = OK; + base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path); if (err) { return err; } + if (base.is_null() && !base->is_valid()) { + return ERR_COMPILATION_FAILED; + } } } + + p_script->member_indices = base->member_indices; + native = base->native; + p_script->native = native; } break; default: { _set_error("Parser bug: invalid inheritance.", p_class); @@ -1889,91 +2109,171 @@ 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(), p_script); - 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; + } else { + prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; + } #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; - } + p_script->doc_variables[name] = variable->doc_description; #endif - } 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; + if (variable->initializer != nullptr && variable->initializer->is_constant) { + p_script->member_default_values[name] = variable->initializer->reduced_value; + } else { + p_script->member_default_values.erase(name); + } + p_script->member_lines[name] = variable->start_line; #endif - } - - 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); + p_script->constants.insert(name, constant->initializer->reduced_value); +#ifdef TOOLS_ENABLED + p_script->member_lines[name] = constant->start_line; + if (constant->doc_description != String()) { + p_script->doc_constants[name] = constant->doc_description; + } +#endif + } break; - GDScriptParser::ConstantNode *constant = static_cast<GDScriptParser::ConstantNode *>(E->get().expression); + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + const GDScriptParser::EnumNode::Value &enum_value = member.enum_value; + StringName name = enum_value.identifier->name; - p_script->constants.insert(name, constant->value); + p_script->constants.insert(name, enum_value.value); #ifdef TOOLS_ENABLED - - p_script->member_lines[name] = E->get().expression->line; + p_script->member_lines[name] = enum_value.identifier->start_line; + if (!p_script->doc_enums.has("@unnamed_enums")) { + p_script->doc_enums["@unnamed_enums"] = DocData::EnumDoc(); + p_script->doc_enums["@unnamed_enums"].name = "@unnamed_enums"; + } + DocData::ConstantDoc const_doc; + const_doc.name = enum_value.identifier->name; + const_doc.value = Variant(enum_value.value).operator String(); // TODO-DOC: enum value currently is int. + const_doc.description = enum_value.doc_description; + p_script->doc_enums["@unnamed_enums"].values.push_back(const_doc); #endif - } + } break; + + case GDScriptParser::ClassNode::Member::SIGNAL: { + const GDScriptParser::SignalNode *signal = member.signal; + StringName name = signal->identifier->name; - for (int i = 0; i < p_class->_signals.size(); i++) { + GDScript *c = p_script; - StringName name = p_class->_signals[i].name; + 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; + } + } - GDScript *c = p_script; + if (native.is_valid()) { + if (ClassDB::has_signal(native->get_name(), name)) { + _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); + return ERR_ALREADY_EXISTS; + } + } - while (c) { + 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; +#ifdef TOOLS_ENABLED + if (!signal->doc_description.is_empty()) { + p_script->doc_signals[name] = signal->doc_description; + } +#endif + } break; - if (c->_signals.has(name)) { - _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } + case GDScriptParser::ClassNode::Member::ENUM: { + const GDScriptParser::EnumNode *enum_n = member.m_enum; - if (c->base.is_valid()) { - c = c->base.ptr(); - } else { - c = nullptr; - } - } + // 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; + } - 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; - } + p_script->constants.insert(enum_n->identifier->name, new_enum); +#ifdef TOOLS_ENABLED + p_script->member_lines[enum_n->identifier->name] = enum_n->start_line; + p_script->doc_enums[enum_n->identifier->name] = DocData::EnumDoc(); + p_script->doc_enums[enum_n->identifier->name].name = enum_n->identifier->name; + p_script->doc_enums[enum_n->identifier->name].description = enum_n->doc_description; + for (int j = 0; j < enum_n->values.size(); j++) { + DocData::ConstantDoc const_doc; + const_doc.name = enum_n->values[j].identifier->name; + const_doc.value = Variant(enum_n->values[j].value).operator String(); + const_doc.description = enum_n->values[j].doc_description; + p_script->doc_enums[enum_n->identifier->name].values.push_back(const_doc); + } +#endif + } break; + default: + break; // Nothing to do here. } - - p_script->_signals[name] = p_class->_signals[i].arguments; } parsed_classes.insert(p_script); @@ -1981,21 +2281,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 +2313,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 +2367,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 +2401,6 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa } #endif } else { - GDScriptInstance *gi = static_cast<GDScriptInstance *>(si); gi->reload_members(); } @@ -2095,11 +2410,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 +2429,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 +2437,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 +2462,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 +2480,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..00953ad752 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,107 +31,86 @@ #ifndef GDSCRIPT_COMPILER_H #define GDSCRIPT_COMPILER_H -#include "core/set.h" +#include "core/templates/set.h" #include "gdscript.h" +#include "gdscript_codegen.h" +#include "gdscript_function.h" #include "gdscript_parser.h" class GDScriptCompiler { - - const GDScriptParser *parser; + const GDScriptParser *parser = nullptr; Set<GDScript *> parsed_classes; Set<GDScript *> parsing_classes; - GDScript *main_script; - struct CodeGen { + GDScript *main_script = nullptr; - GDScript *script; - const GDScriptParser::ClassNode *class_node; - const GDScriptParser::FunctionNode *function_node; - bool debug_stack; - - List<Map<StringName, int>> stack_id_stack; - Map<StringName, int> stack_identifiers; - - List<GDScriptFunction::StackDebug> stack_debug; - List<Map<StringName, int>> block_identifier_stack; - Map<StringName, int> block_identifiers; - - void add_stack_identifier(const StringName &p_id, int p_stackpos) { - stack_identifiers[p_id] = p_stackpos; - if (debug_stack) { - block_identifiers[p_id] = p_stackpos; - GDScriptFunction::StackDebug sd; - sd.added = true; - sd.line = current_line; - sd.identifier = p_id; - sd.pos = p_stackpos; - stack_debug.push_back(sd); - } + struct CodeGen { + GDScript *script = nullptr; + const GDScriptParser::ClassNode *class_node = nullptr; + const GDScriptParser::FunctionNode *function_node = nullptr; + StringName function_name; + GDScriptCodeGenerator *generator = nullptr; + Map<StringName, GDScriptCodeGenerator::Address> parameters; + Map<StringName, GDScriptCodeGenerator::Address> locals; + List<Map<StringName, GDScriptCodeGenerator::Address>> locals_stack; + + GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) { + uint32_t addr = generator->add_local(p_name, p_type); + locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_VARIABLE, addr, p_type); + return locals[p_name]; } - void push_stack_identifiers() { - stack_id_stack.push_back(stack_identifiers); - if (debug_stack) { - - block_identifier_stack.push_back(block_identifiers); - block_identifiers.clear(); - } + GDScriptCodeGenerator::Address add_local_constant(const StringName &p_name, const Variant &p_value) { + uint32_t addr = generator->add_local_constant(p_name, p_value); + locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_CONSTANT, addr); + return locals[p_name]; } - void pop_stack_identifiers() { - stack_identifiers = stack_id_stack.back()->get(); - stack_id_stack.pop_back(); - - if (debug_stack) { - for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { + GDScriptCodeGenerator::Address add_temporary(const GDScriptDataType &p_type = GDScriptDataType()) { + uint32_t addr = generator->add_temporary(); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::TEMPORARY, addr, p_type); + } - GDScriptFunction::StackDebug sd; - sd.added = false; - sd.identifier = E->key(); - sd.line = current_line; - sd.pos = E->get(); - stack_debug.push_back(sd); + GDScriptCodeGenerator::Address add_constant(const Variant &p_constant) { + GDScriptDataType type; + type.has_type = true; + type.kind = GDScriptDataType::BUILTIN; + type.builtin_type = p_constant.get_type(); + if (type.builtin_type == Variant::OBJECT) { + Object *obj = p_constant; + if (obj) { + type.kind = GDScriptDataType::NATIVE; + type.native_type = obj->get_class_name(); + + Ref<Script> script = obj->get_script(); + if (script.is_valid()) { + type.script_type = script.ptr(); + Ref<GDScript> gdscript = script; + if (gdscript.is_valid()) { + type.kind = GDScriptDataType::GDSCRIPT; + } else { + type.kind = GDScriptDataType::SCRIPT; + } + } + } else { + type.builtin_type = Variant::NIL; } - block_identifiers = block_identifier_stack.back()->get(); - block_identifier_stack.pop_back(); } - } - HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; - Map<StringName, int> name_map; -#ifdef TOOLS_ENABLED - Vector<StringName> named_globals; -#endif - - int get_name_map_pos(const StringName &p_identifier) { - int ret; - if (!name_map.has(p_identifier)) { - ret = name_map.size(); - name_map[p_identifier] = ret; - } else { - ret = name_map[p_identifier]; - } - return ret; + uint32_t addr = generator->add_or_get_constant(p_constant); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CONSTANT, addr, type); } - int get_constant_pos(const Variant &p_constant) { - if (constant_map.has(p_constant)) - return constant_map[p_constant]; - int pos = constant_map.size(); - constant_map[p_constant] = pos; - return pos; + void start_block() { + Map<StringName, GDScriptCodeGenerator::Address> old_locals = locals; + locals_stack.push_back(old_locals); + generator->start_block(); } - Vector<int> opcodes; - void alloc_stack(int p_level) { - if (p_level >= stack_max) stack_max = p_level + 1; - } - void alloc_call(int p_params) { - if (p_params >= call_max) call_max = p_params; + void end_block() { + locals = locals_stack.back()->get(); + locals_stack.pop_back(); + generator->end_block(); } - - int current_line; - int stack_max; - int call_max; }; bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); @@ -139,15 +118,18 @@ 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); + Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const; - int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::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); + GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); + void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); + Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); + Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); 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 +137,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_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp new file mode 100644 index 0000000000..17cb5e3c96 --- /dev/null +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -0,0 +1,842 @@ +/*************************************************************************/ +/* gdscript_disassembler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef DEBUG_ENABLED + +#include "gdscript_function.h" + +#include "core/string/string_builder.h" +#include "gdscript.h" + +static String _get_variant_string(const Variant &p_variant) { + String txt; + if (p_variant.get_type() == Variant::STRING) { + txt = "\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::STRING_NAME) { + txt = "&\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::NODE_PATH) { + txt = "^\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::OBJECT) { + Object *obj = p_variant; + if (!obj) { + txt = "null"; + } else { + GDScriptNativeClass *cls = Object::cast_to<GDScriptNativeClass>(obj); + if (cls) { + txt += cls->get_name(); + txt += " (class)"; + } else { + txt = obj->get_class(); + if (obj->get_script_instance()) { + txt += "(" + obj->get_script_instance()->get_script()->get_path() + ")"; + } + } + } + } else { + txt = p_variant; + } + return txt; +} + +static String _disassemble_address(const GDScript *p_script, const GDScriptFunction &p_function, int p_address) { + int addr = p_address & GDScriptFunction::ADDR_MASK; + + switch (p_address >> GDScriptFunction::ADDR_BITS) { + case GDScriptFunction::ADDR_TYPE_SELF: { + return "self"; + } break; + case GDScriptFunction::ADDR_TYPE_CLASS: { + return "class"; + } break; + case GDScriptFunction::ADDR_TYPE_MEMBER: { + return "member(" + p_script->debug_get_member_by_index(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT: { + return "class_const(" + p_function.get_global_name(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: { + return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_STACK: { + return "stack(" + itos(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_STACK_VARIABLE: { + return "var_stack(" + itos(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_GLOBAL: { + return "global(" + _get_variant_string(GDScriptLanguage::get_singleton()->get_global_array()[addr]) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL: { + return "named_global(" + p_function.get_global_name(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_NIL: { + return "nil"; + } break; + } + + return "<err>"; +} + +void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { +#define DADDR(m_ip) (_disassemble_address(_script, *this, _code_ptr[ip + m_ip])) + + for (int ip = 0; ip < _code_size;) { + StringBuilder text; + int incr = 0; + + text += " "; + text += itos(ip); + text += ": "; + + // This makes the compiler complain if some opcode is unchecked in the switch. + Opcode code = Opcode(_code_ptr[ip] & INSTR_MASK); + int instr_var_args = (_code_ptr[ip] & INSTR_ARGS_MASK) >> INSTR_BITS; + + switch (code) { + case OPCODE_OPERATOR: { + int operation = _code_ptr[ip + 4]; + + text += "operator "; + + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " "; + text += Variant::get_operator_name(Variant::Operator(operation)); + text += " "; + text += DADDR(2); + + incr += 5; + } break; + case OPCODE_OPERATOR_VALIDATED: { + text += "validated operator "; + + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " <operator function> "; + text += DADDR(2); + + incr += 5; + } break; + case OPCODE_EXTENDS_TEST: { + text += "is object "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " is "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_IS_BUILTIN: { + text += "is builtin "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " is "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3])); + + incr += 4; + } break; + case OPCODE_SET_KEYED: { + text += "set keyed "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "] = "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_SET_KEYED_VALIDATED: { + text += "set keyed validated "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "] = "; + text += DADDR(3); + + incr += 5; + } break; + case OPCODE_SET_INDEXED_VALIDATED: { + text += "set indexed validated "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "] = "; + text += DADDR(3); + + incr += 5; + } break; + case OPCODE_GET_KEYED: { + text += "get keyed "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "]"; + + incr += 4; + } break; + case OPCODE_GET_KEYED_VALIDATED: { + text += "get keyed validated "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "]"; + + incr += 5; + } break; + case OPCODE_GET_INDEXED_VALIDATED: { + text += "get indexed validated "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "]"; + + incr += 5; + } break; + case OPCODE_SET_NAMED: { + text += "set_named "; + text += DADDR(1); + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 3]]; + text += "\"] = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_SET_NAMED_VALIDATED: { + text += "set_named validated "; + text += DADDR(1); + text += "[\""; + text += "<unknown name>"; + text += "\"] = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_GET_NAMED: { + text += "get_named "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 3]]; + text += "\"]"; + + incr += 4; + } break; + case OPCODE_GET_NAMED_VALIDATED: { + text += "get_named validated "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += "[\""; + text += "<unknown name>"; + text += "\"]"; + + incr += 4; + } break; + case OPCODE_SET_MEMBER: { + text += "set_member "; + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 2]]; + text += "\"] = "; + text += DADDR(1); + + incr += 3; + } break; + case OPCODE_GET_MEMBER: { + text += "get_member "; + text += DADDR(1); + text += " = "; + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 2]]; + text += "\"]"; + + incr += 3; + } break; + case OPCODE_ASSIGN: { + text += "assign "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 3; + } break; + case OPCODE_ASSIGN_TRUE: { + text += "assign "; + text += DADDR(1); + text += " = true"; + + incr += 2; + } break; + case OPCODE_ASSIGN_FALSE: { + text += "assign "; + text += DADDR(1); + text += " = false"; + + incr += 2; + } break; + case OPCODE_ASSIGN_TYPED_BUILTIN: { + text += "assign typed builtin ("; + text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 3]); + text += ") "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_ASSIGN_TYPED_NATIVE: { + text += "assign typed native ("; + text += DADDR(3); + text += ") "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_ASSIGN_TYPED_SCRIPT: { + Variant script = _constants_ptr[_code_ptr[ip + 3]]; + Script *sc = Object::cast_to<Script>(script.operator Object *()); + + text += "assign typed script ("; + text += sc->get_path(); + text += ") "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_CAST_TO_BUILTIN: { + text += "cast builtin "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " as "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 1])); + + incr += 4; + } break; + case OPCODE_CAST_TO_NATIVE: { + text += "cast native "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " as "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_CAST_TO_SCRIPT: { + text += "cast "; + text += DADDR(2); + text += " = "; + text += DADDR(1); + text += " as "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_CONSTRUCT: { + Variant::Type t = Variant::Type(_code_ptr[ip + 3 + instr_var_args]); + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += "construct "; + text += DADDR(1 + argc); + text += " = "; + + text += Variant::get_type_name(t) + "("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(i + 1); + } + text += ")"; + + incr = 3 + instr_var_args; + } break; + case OPCODE_CONSTRUCT_VALIDATED: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += "construct validated "; + text += DADDR(1 + argc); + text += " = "; + + text += "<unkown type>("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(i + 1); + } + text += ")"; + + incr = 3 + instr_var_args; + } break; + case OPCODE_CONSTRUCT_ARRAY: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += " make_array "; + text += DADDR(1 + argc); + text += " = ["; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + + text += "]"; + + incr += 3 + argc; + } break; + case OPCODE_CONSTRUCT_DICTIONARY: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += "make_dict "; + text += DADDR(1 + argc * 2); + text += " = {"; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i * 2 + 0); + text += ": "; + text += DADDR(1 + i * 2 + 1); + } + + text += "}"; + + incr += 3 + argc * 2; + } break; + case OPCODE_CALL: + case OPCODE_CALL_RETURN: + case OPCODE_CALL_ASYNC: { + bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_RETURN; + bool async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC; + + if (ret) { + text += "call-ret "; + } else if (async) { + text += "call-async "; + } else { + text += "call "; + } + + int argc = _code_ptr[ip + 1 + instr_var_args]; + if (ret || async) { + text += DADDR(2 + argc) + " = "; + } + + text += DADDR(1 + argc) + "."; + text += String(_global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 5 + argc; + } break; + case OPCODE_CALL_METHOD_BIND: + case OPCODE_CALL_METHOD_BIND_RET: { + bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET; + + if (ret) { + text += "call-method_bind-ret "; + } else { + text += "call-method_bind "; + } + + MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + if (ret) { + text += DADDR(2 + argc) + " = "; + } + + text += DADDR(1 + argc) + "."; + text += method->get_name(); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 5 + argc; + } break; + case OPCODE_CALL_PTRCALL_NO_RETURN: { + text += "call-ptrcall (no return) "; + + MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += DADDR(1 + argc) + "."; + text += method->get_name(); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 5 + argc; + } break; + +#define DISASSEMBLE_PTRCALL(m_type) \ + case OPCODE_CALL_PTRCALL_##m_type: { \ + text += "call-ptrcall (return "; \ + text += #m_type; \ + text += ") "; \ + MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \ + int argc = _code_ptr[ip + 1 + instr_var_args]; \ + text += DADDR(2 + argc) + " = "; \ + text += DADDR(1 + argc) + "."; \ + text += method->get_name(); \ + text += "("; \ + for (int i = 0; i < argc; i++) { \ + if (i > 0) \ + text += ", "; \ + text += DADDR(1 + i); \ + } \ + text += ")"; \ + incr = 5 + argc; \ + } break + + DISASSEMBLE_PTRCALL(BOOL); + DISASSEMBLE_PTRCALL(INT); + DISASSEMBLE_PTRCALL(FLOAT); + DISASSEMBLE_PTRCALL(STRING); + DISASSEMBLE_PTRCALL(VECTOR2); + DISASSEMBLE_PTRCALL(VECTOR2I); + DISASSEMBLE_PTRCALL(RECT2); + DISASSEMBLE_PTRCALL(RECT2I); + DISASSEMBLE_PTRCALL(VECTOR3); + DISASSEMBLE_PTRCALL(VECTOR3I); + DISASSEMBLE_PTRCALL(TRANSFORM2D); + DISASSEMBLE_PTRCALL(PLANE); + DISASSEMBLE_PTRCALL(AABB); + DISASSEMBLE_PTRCALL(BASIS); + DISASSEMBLE_PTRCALL(TRANSFORM); + DISASSEMBLE_PTRCALL(COLOR); + DISASSEMBLE_PTRCALL(STRING_NAME); + DISASSEMBLE_PTRCALL(NODE_PATH); + DISASSEMBLE_PTRCALL(RID); + DISASSEMBLE_PTRCALL(QUAT); + DISASSEMBLE_PTRCALL(OBJECT); + DISASSEMBLE_PTRCALL(CALLABLE); + DISASSEMBLE_PTRCALL(SIGNAL); + DISASSEMBLE_PTRCALL(DICTIONARY); + DISASSEMBLE_PTRCALL(ARRAY); + DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY); + DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY); + + case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + + text += "call-builtin-method validated "; + + text += DADDR(2 + argc) + " = "; + + text += DADDR(1) + "."; + text += "<unknown method>"; + + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 5 + argc; + } break; + case OPCODE_CALL_UTILITY: { + text += "call-utility "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(1 + argc) + " = "; + + text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_UTILITY_VALIDATED: { + text += "call-utility "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(1 + argc) + " = "; + + text += "<unkown function>"; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_GDSCRIPT_UTILITY: { + text += "call-gscript-utility "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(1 + argc) + " = "; + + text += "<unknown function>"; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_SELF_BASE: { + text += "call-self-base "; + + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(2 + argc) + " = "; + + text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_AWAIT: { + text += "await "; + text += DADDR(1); + + incr += 2; + } break; + case OPCODE_AWAIT_RESUME: { + text += "await resume "; + text += DADDR(1); + + incr = 2; + } break; + case OPCODE_JUMP: { + text += "jump "; + text += itos(_code_ptr[ip + 1]); + + incr = 2; + } break; + case OPCODE_JUMP_IF: { + text += "jump-if "; + text += DADDR(1); + text += " to "; + text += itos(_code_ptr[ip + 2]); + + incr = 3; + } break; + case OPCODE_JUMP_IF_NOT: { + text += "jump-if-not "; + text += DADDR(1); + text += " to "; + text += itos(_code_ptr[ip + 2]); + + incr = 3; + } break; + case OPCODE_JUMP_TO_DEF_ARGUMENT: { + text += "jump-to-default-argument "; + + incr = 1; + } break; + case OPCODE_RETURN: { + text += "return "; + text += DADDR(1); + + incr = 2; + } break; + +#define DISASSEMBLE_ITERATE(m_type) \ + case OPCODE_ITERATE_##m_type: { \ + text += "for-loop (typed "; \ + text += #m_type; \ + text += ") "; \ + text += DADDR(3); \ + text += " in "; \ + text += DADDR(2); \ + text += " counter "; \ + text += DADDR(1); \ + text += " end "; \ + text += itos(_code_ptr[ip + 4]); \ + incr += 5; \ + } break + +#define DISASSEMBLE_ITERATE_BEGIN(m_type) \ + case OPCODE_ITERATE_BEGIN_##m_type: { \ + text += "for-init (typed "; \ + text += #m_type; \ + text += ") "; \ + text += DADDR(3); \ + text += " in "; \ + text += DADDR(2); \ + text += " counter "; \ + text += DADDR(1); \ + text += " end "; \ + text += itos(_code_ptr[ip + 4]); \ + incr += 5; \ + } break + +#define DISASSEMBLE_ITERATE_TYPES(m_macro) \ + m_macro(INT); \ + m_macro(FLOAT); \ + m_macro(VECTOR2); \ + m_macro(VECTOR2I); \ + m_macro(VECTOR3); \ + m_macro(VECTOR3I); \ + m_macro(STRING); \ + m_macro(DICTIONARY); \ + m_macro(ARRAY); \ + m_macro(PACKED_BYTE_ARRAY); \ + m_macro(PACKED_INT32_ARRAY); \ + m_macro(PACKED_INT64_ARRAY); \ + m_macro(PACKED_FLOAT32_ARRAY); \ + m_macro(PACKED_FLOAT64_ARRAY); \ + m_macro(PACKED_STRING_ARRAY); \ + m_macro(PACKED_VECTOR2_ARRAY); \ + m_macro(PACKED_VECTOR3_ARRAY); \ + m_macro(PACKED_COLOR_ARRAY); \ + m_macro(OBJECT) + + case OPCODE_ITERATE_BEGIN: { + text += "for-init "; + text += DADDR(3); + text += " in "; + text += DADDR(2); + text += " counter "; + text += DADDR(1); + text += " end "; + text += itos(_code_ptr[ip + 4]); + + incr += 5; + } break; + DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE_BEGIN); + case OPCODE_ITERATE: { + text += "for-loop "; + text += DADDR(2); + text += " in "; + text += DADDR(2); + text += " counter "; + text += DADDR(1); + text += " end "; + text += itos(_code_ptr[ip + 4]); + + incr += 5; + } break; + DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE); + case OPCODE_LINE: { + int line = _code_ptr[ip + 1] - 1; + if (line >= 0 && line < p_code_lines.size()) { + text += "line "; + text += itos(line + 1); + text += ": "; + text += p_code_lines[line]; + } else { + text += ""; + } + + incr += 2; + } break; + case OPCODE_ASSERT: { + text += "assert ("; + text += DADDR(1); + text += ", "; + text += DADDR(2); + text += ")"; + + incr += 3; + } break; + case OPCODE_BREAKPOINT: { + text += "breakpoint"; + + incr += 1; + } break; + case OPCODE_END: { + text += "== END =="; + + incr += 1; + } break; + } + + ip += incr; + if (text.get_string_length() > 0) { + print_line(text.as_string()); + } + } +} + +#endif diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index ab3228d076..b17971cf93 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -30,30 +30,32 @@ #include "gdscript.h" -#include "core/engine.h" -#include "core/global_constants.h" +#include "core/config/engine.h" +#include "core/core_constants.h" #include "core/os/file_access.h" +#include "gdscript_analyzer.h" #include "gdscript_compiler.h" +#include "gdscript_parser.h" +#include "gdscript_tokenizer.h" +#include "gdscript_utility_functions.h" #ifdef TOOLS_ENABLED +#include "core/config/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 +111,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.is_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.is_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 +156,74 @@ 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; +} +bool GDScriptLanguage::supports_documentation() 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 +233,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 +244,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 +257,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 +279,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 +314,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 +339,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 +358,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 +365,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,24 +376,27 @@ 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; - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (E->key() == GlobalConstants::get_global_constant_name(i)) { + for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { + if (E->key() == CoreConstants::get_global_constant_name(i)) { skip = true; break; } } - if (skip) + if (skip) { continue; + } p_globals->push_back(E->key()); p_values->push_back(var); @@ -391,23 +404,22 @@ 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 { + List<StringName> functions; + GDScriptUtilityFunctions::get_function_list(&functions); - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - - p_functions->push_back(GDScriptFunctions::get_info(GDScriptFunctions::Function(i))); + for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { + p_functions->push_back(GDScriptUtilityFunctions::get_function_info(E->get())); } - //not really "functions", but.. + // Not really "functions", but show in documentation. { MethodInfo mi; mi.name = "preload"; @@ -417,16 +429,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 +439,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 +461,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,12 +470,13 @@ 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); - if (!type.empty() && type != "var") { + if (!type.is_empty() && type != "var") { s += ": " + type; } } @@ -488,54 +489,48 @@ 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 + +#define COMPLETION_RECURSION_LIMIT 200 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 +553,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 +563,573 @@ 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, int p_recursion_depth); + +static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { + ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); + + 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); + if (member.constant->initializer) { + option.default_value = member.constant->initializer->reduced_value; + } + 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) { + 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, p_recursion_depth + 1); +} + +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { + ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); + + GDScriptParser::DataType base_type = p_base.type; + bool _static = base_type.is_meta_type; + + 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, p_recursion_depth + 1); + // 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, tmp, 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, int p_recursion_depth) { + if (!p_only_functions && p_context.current_suite) { + // This includes function parameters, since they are also locals. + _find_identifiers_in_suite(p_context.current_suite, r_result); + } + + if (p_context.current_class) { + _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); + } + + List<StringName> functions; + GDScriptUtilityFunctions::get_function_list(&functions); + + for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { + MethodInfo function = GDScriptUtilityFunctions::get_function_info(E->get()); + ScriptCodeCompletionOption option(String(E->get()), 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[] = { + "false", "PI", "TAU", "INF", "NAN", "self", "true", "breakpoint", "tool", "super", + "break", "continue", "pass", "return", + 0 + }; + + const char **kw = _keywords; + while (*kw) { + ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_result.insert(option.display, option); + kw++; + } + + static const char *_keywords_with_space[] = { + "and", "in", "not", "or", "as", "class", "extends", "is", "func", "signal", "await", + "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while", + 0 + }; + + const char **kws = _keywords_with_space; + while (*kws) { + ScriptCodeCompletionOption option(*kws, ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + option.insert_text += " "; + r_result.insert(option.display, option); + kws++; + } + + static const char *_keywords_with_args[] = { + "assert", "preload", + 0 + }; + + const char **kwa = _keywords_with_args; + while (*kwa) { + ScriptCodeCompletionOption option(*kwa, ScriptCodeCompletionOption::KIND_FUNCTION); + option.insert_text += "("; + r_result.insert(option.display, option); + kwa++; + } + + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { + if (!E->value().is_singleton) { + 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 +1148,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 +1170,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 +1181,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; +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); - switch (p_gdtype.kind) { - case GDScriptDataType::UNINITIALIZED: { - ERR_PRINT("Uninitialized completion. Please report a bug."); - } break; - case GDScriptDataType::BUILTIN: { - ci.type.kind = GDScriptParser::DataType::BUILTIN; - } break; - case GDScriptDataType::NATIVE: { - ci.type.kind = GDScriptParser::DataType::NATIVE; - } break; - case GDScriptDataType::GDSCRIPT: { - ci.type.kind = GDScriptParser::DataType::GDSCRIPT; - } break; - case GDScriptDataType::SCRIPT: { - ci.type.kind = GDScriptParser::DataType::SCRIPT; - } break; - } - return ci; -} - -static bool _guess_identifier_type(GDScriptCompletionContext &p_context, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); -static bool _guess_identifier_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_identifier, GDScriptCompletionIdentifier &r_type); -static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, GDScriptCompletionIdentifier &r_type); - -static bool _guess_expression_type(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_expression, GDScriptCompletionIdentifier &r_type) { +static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::ExpressionNode *p_expression, GDScriptCompletionIdentifier &r_type) { bool found = false; - if (++p_context.depth > 100) { - print_error("Maximum _guess_expression_type depth limit reached. Please file a bugreport."); - return false; - } - - switch (p_expression->type) { - case GDScriptParser::Node::TYPE_CONSTANT: { - const GDScriptParser::ConstantNode *cn = static_cast<const GDScriptParser::ConstantNode *>(p_expression); - r_type = _type_from_variant(cn->value); - found = true; - } break; - case GDScriptParser::Node::TYPE_SELF: { - if (p_context._class) { - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::CLASS; - r_type.type.class_type = const_cast<GDScriptParser::ClassNode *>(p_context._class); - r_type.type.is_constant = true; - r_type.value = p_context.base; + if (p_expression->is_constant) { + // Already has a value, so just use that. + r_type = _type_from_variant(p_expression->reduced_value); + found = true; + } else { + switch (p_expression->type) { + case GDScriptParser::Node::LITERAL: { + const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression); + r_type = _type_from_variant(literal->value); found = true; - } - } break; - case GDScriptParser::Node::TYPE_IDENTIFIER: { - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); - found = _guess_identifier_type(p_context, id->name, r_type); - } break; - case GDScriptParser::Node::TYPE_DICTIONARY: { - // Try to recreate the dictionary - const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); - Dictionary d; - bool full = true; - for (int i = 0; i < dn->elements.size(); i++) { - GDScriptCompletionIdentifier key; - if (_guess_expression_type(p_context, dn->elements[i].key, key)) { - GDScriptCompletionIdentifier value; - if (_guess_expression_type(p_context, dn->elements[i].value, value)) { - if (!value.type.is_constant) { + } break; + case GDScriptParser::Node::SELF: { + if (p_context.current_class) { + r_type.type.kind = GDScriptParser::DataType::CLASS; + r_type.type.type_source = GDScriptParser::DataType::INFERRED; + r_type.type.is_constant = true; + r_type.type.class_type = p_context.current_class; + r_type.value = p_context.base; + found = true; + } + } break; + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); + found = _guess_identifier_type(p_context, id->name, r_type); + } break; + case GDScriptParser::Node::DICTIONARY: { + // Try to recreate the dictionary. + const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); + Dictionary d; + bool full = true; + for (int i = 0; i < dn->elements.size(); i++) { + GDScriptCompletionIdentifier key; + if (_guess_expression_type(p_context, dn->elements[i].key, key)) { + if (!key.type.is_constant) { + full = false; + break; + } + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, dn->elements[i].value, value)) { + if (!value.type.is_constant) { + full = false; + break; + } + d[key.value] = value.value; + } else { full = false; break; } - d[key.value] = value.value; } else { full = false; break; } - } else { - full = false; - break; } - } - if (full) { - // If not fully constant, setting this value is detrimental to the inference - r_type.value = d; - r_type.type.is_constant = true; - } - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::BUILTIN; - r_type.type.builtin_type = Variant::DICTIONARY; - } break; - case GDScriptParser::Node::TYPE_ARRAY: { - // Try to recreate the array - const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); - Array a; - bool full = true; - a.resize(an->elements.size()); - for (int i = 0; i < an->elements.size(); i++) { - GDScriptCompletionIdentifier value; - if (_guess_expression_type(p_context, an->elements[i], value)) { - a[i] = value.value; - } else { - full = false; - break; + if (full) { + r_type.value = d; + r_type.type.is_constant = true; } - } - if (full) { - // If not fully constant, setting this value is detrimental to the inference - r_type.value = a; - } - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::BUILTIN; - r_type.type.builtin_type = Variant::ARRAY; - } break; - case GDScriptParser::Node::TYPE_CAST: { - const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptCompletionIdentifier value; - if (_guess_expression_type(p_context, cn->source_node, r_type)) { - r_type.type = cn->get_datatype(); + r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_type.type.kind = GDScriptParser::DataType::BUILTIN; + r_type.type.builtin_type = Variant::DICTIONARY; found = true; - } - } break; - case GDScriptParser::Node::TYPE_OPERATOR: { - const GDScriptParser::OperatorNode *op = static_cast<const GDScriptParser::OperatorNode *>(p_expression); - switch (op->op) { - case GDScriptParser::OperatorNode::OP_CALL: { - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - r_type.type.has_type = true; - r_type.type.kind = GDScriptParser::DataType::BUILTIN; - r_type.type.builtin_type = tn->vtype; - found = true; - break; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - const GDScriptParser::BuiltInFunctionNode *bin = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - MethodInfo mi = GDScriptFunctions::get_info(bin->function); - r_type = _type_from_property(mi.return_val); - found = true; + } break; + case GDScriptParser::Node::ARRAY: { + // Try to recreate the array + const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); + Array a; + bool full = true; + a.resize(an->elements.size()); + for (int i = 0; i < an->elements.size(); i++) { + GDScriptCompletionIdentifier value; + if (_guess_expression_type(p_context, an->elements[i], value)) { + if (value.type.is_constant) { + a[i] = value.value; + } else { + full = false; + break; + } + } else { + full = false; break; - } else if (op->arguments.size() >= 2 && op->arguments[1]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - StringName id = static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name; + } + } + 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 (GDScriptUtilityFunctions::function_exists(call->function_name)) { + MethodInfo mi = GDScriptUtilityFunctions::get_function_info(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 +1476,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 +1485,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 +1515,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 +1537,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); } @@ -1063,7 +1560,8 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G found = _guess_identifier_type_from_base(c, base, id, r_type); } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) { Callable::CallError err; - Variant base_val = Variant::construct(base.type.builtin_type, nullptr, 0, err); + Variant base_val; + Variant::construct(base.type.builtin_type, base_val, nullptr, 0, err); bool valid = false; Variant res = base_val.get(index.value, &valid); if (valid) { @@ -1073,93 +1571,79 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G found = true; } } - } break; - default: { - if (op->arguments.size() < 2) { - found = false; - break; - } + } + } break; + case GDScriptParser::Node::BINARY_OPERATOR: { + const GDScriptParser::BinaryOpNode *op = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - 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: { - } - } + if (op->variant_op == Variant::OP_MAX) { + break; + } - if (vop == Variant::OP_MAX) { - break; - } + GDScriptParser::CompletionContext context = p_context; + context.current_line = op->start_line; - GDScriptCompletionContext context = p_context; - context.line = op->line; + GDScriptCompletionIdentifier p1; + GDScriptCompletionIdentifier p2; - GDScriptCompletionIdentifier p1; - GDScriptCompletionIdentifier p2; + if (!_guess_expression_type(context, op->left_operand, p1)) { + found = false; + break; + } - if (!_guess_expression_type(context, op->arguments[0], p1)) { - found = false; - break; - } + if (!_guess_expression_type(context, op->right_operand, p2)) { + found = false; + break; + } - if (!_guess_expression_type(context, op->arguments[1], p2)) { - found = false; - break; - } + Callable::CallError ce; + bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; + Variant d1; + Variant::construct(p1.type.builtin_type, d1, nullptr, 0, ce); + Variant d2; + Variant::construct(p2.type.builtin_type, d2, nullptr, 0, ce); - 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; - } + Variant v1 = (v1_use_value) ? p1.value : d1; + bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; + Variant v2 = (v2_use_value) ? p2.value : d2; + // avoid potential invalid ops + if ((op->variant_op == Variant::OP_DIVIDE || op->variant_op == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { + v2 = 1; + 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,307 +1654,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()) { @@ -1481,7 +1947,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()) { @@ -1504,40 +1970,33 @@ 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; } break; case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); + Variant tmp; + Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return false; @@ -1557,116 +2016,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()) { @@ -1691,12 +2133,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) { @@ -1707,7 +2146,8 @@ static bool _guess_method_return_type_from_base(GDScriptCompletionContext &p_con } break; case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); + Variant tmp; + Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return false; } @@ -1729,106 +2169,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 < CoreConstants::get_global_constant_count(); i++) { + if (CoreConstants::get_global_constant_enum(i) == current_enum) { + ScriptCodeCompletionOption option(CoreConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); + } + } } } else { String class_name = p_enum_hint.get_slice(".", 0); @@ -1848,498 +2209,60 @@ static void _find_enumeration_candidates(const String p_enum_hint, Map<String, S } } -static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) { - for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) { - if (E->get()->line < p_context.line) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_VARIABLE); - r_result.insert(option.display, option); - } - } - if (p_context.block->parent_block) { - GDScriptCompletionContext c = p_context; - c.block = p_context.block->parent_block; - _find_identifiers_in_block(c, r_result); - } -} - -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result); - -static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) { - if (!p_parent_only) { - if (!p_static && !p_only_functions) { - for (int i = 0; i < p_context._class->variables.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->variables[i].identifier, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - - if (!p_only_functions) { - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - for (int i = 0; i < p_context._class->subclasses.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->subclasses[i]->name, ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - } - - for (int i = 0; i < p_context._class->static_functions.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->static_functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); - if (p_context._class->static_functions[i]->arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - if (!p_static) { - for (int i = 0; i < p_context._class->functions.size(); i++) { - ScriptCodeCompletionOption option(p_context._class->functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); - if (p_context._class->functions[i]->arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - } - } - - // Parents - GDScriptCompletionIdentifier base_type; - base_type.type = p_context._class->base_type; - base_type.type.is_meta_type = p_static; - base_type.value = p_context.base; - - GDScriptCompletionContext c = p_context; - c.block = nullptr; - c.function = nullptr; - - _find_identifiers_in_base(c, base_type, p_only_functions, r_result); -} - -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { - GDScriptParser::DataType base_type = p_base.type; - bool _static = base_type.is_meta_type; - - if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { - ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION); - option.insert_text += "("; - r_result.insert(option.display, option); - } - - while (base_type.has_type) { - switch (base_type.kind) { - case GDScriptParser::DataType::CLASS: { - GDScriptCompletionContext c = p_context; - c._class = base_type.class_type; - c.block = nullptr; - c.function = nullptr; - _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); - base_type = base_type.class_type->base_type; - } break; - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> script = base_type.script_type; - if (script.is_valid()) { - if (!_static && !p_only_functions) { - if (p_context.base && p_context.base->get_script_instance()) { - List<PropertyInfo> members; - p_context.base->get_script_instance()->get_property_list(&members); - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - if (!p_only_functions) { - for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - } - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { - if (!_static || E->get()->is_static()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get()->get_argument_count()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - } - if (!p_only_functions) { - for (const Map<StringName, Ref<GDScript>>::Element *E = script->get_subclasses().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - } - base_type = GDScriptParser::DataType(); - if (script->get_base().is_valid()) { - base_type.has_type = true; - base_type.kind = GDScriptParser::DataType::GDSCRIPT; - base_type.script_type = script->get_base(); - } else { - base_type.has_type = script->get_instance_base_type() != StringName(); - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = script->get_instance_base_type(); - } - } else { - return; - } - } break; - case GDScriptParser::DataType::SCRIPT: { - Ref<Script> scr = base_type.script_type; - if (scr.is_valid()) { - if (!_static && !p_only_functions) { - List<PropertyInfo> members; - scr->get_script_property_list(&members); - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - if (!p_only_functions) { - Map<StringName, Variant> constants; - scr->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - } - - List<MethodInfo> methods; - scr->get_script_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - Ref<Script> base_script = scr->get_base_script(); - if (base_script.is_valid()) { - base_type.script_type = base_script; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = scr->get_instance_base_type(); - } - } else { - return; - } - } break; - case GDScriptParser::DataType::NATIVE: { - StringName type = base_type.native_type; - if (!ClassDB::class_exists(type)) { - type = String("_") + type; - if (!ClassDB::class_exists(type)) { - return; - } - } - - if (!p_only_functions) { - List<String> constants; - ClassDB::get_integer_constant_list(type, &constants); - for (List<String>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - - if (!_static) { - List<PropertyInfo> pinfo; - ClassDB::get_property_list(type, &pinfo); - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY)) { - continue; - } - if (E->get().name.find("/") != -1) { - continue; - } - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - } - - if (!_static) { - List<MethodInfo> methods; - bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize(); - ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name.begins_with("_")) { - continue; - } - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - } - - return; - } break; - case GDScriptParser::DataType::BUILTIN: { - Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); - if (err.error != Callable::CallError::CALL_OK) { - return; - } - - if (!p_only_functions) { - List<PropertyInfo> members; - tmp.get_property_list(&members); - - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - if (String(E->get().name).find("/") == -1) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } - } - } - - List<MethodInfo> methods; - tmp.get_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - return; - } break; - default: { - return; - } break; - } - } -} - -static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { - - const GDScriptParser::BlockNode *block = p_context.block; - - if (p_context.function) { - - const GDScriptParser::FunctionNode *f = p_context.function; - - for (int i = 0; i < f->arguments.size(); i++) { - ScriptCodeCompletionOption option(f->arguments[i].operator String(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - r_result.insert(option.display, option); - } - } - - if (!p_only_functions && block) { - GDScriptCompletionContext c = p_context; - c.block = block; - _find_identifiers_in_block(c, r_result); - } - - const GDScriptParser::ClassNode *clss = p_context._class; - bool _static = p_context.function && p_context.function->_static; - - while (clss) { - GDScriptCompletionContext c = p_context; - c._class = clss; - c.block = nullptr; - c.function = nullptr; - _find_identifiers_in_class(c, _static, p_only_functions, false, r_result); - _static = true; - clss = clss->owner; - } - - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); - ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION); - if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) { - option.insert_text += "("; - } else { - option.insert_text += "()"; - } - r_result.insert(option.display, option); - } - - static const char *_type_names[Variant::VARIANT_MAX] = { - "null", "bool", "int", "float", "String", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform", - "Color", "StringName", "NodePath", "RID", "Object", "Callable", "Signal", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray", - "PackedVector2Array", "PackedVector3Array", "PackedColorArray" - }; - - for (int i = 0; i < Variant::VARIANT_MAX; i++) { - ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - - static const char *_keywords[] = { - "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert", - "breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield", - "const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif", - "else", "for", "pass", "return", "match", "while", "remote", "master", "puppet", - "remotesync", "mastersync", "puppetsync", - nullptr - }; - - const char **kw = _keywords; - while (*kw) { - ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - r_result.insert(option.display, option); - kw++; - } - - // Autoload singletons - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - String path = ProjectSettings::get_singleton()->get(s); - if (path.begins_with("*")) { - ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CONSTANT); - r_result.insert(option.display, option); - } - } - - // Named scripts - List<StringName> named_scripts; - ScriptServer::get_global_class_list(&named_scripts); - for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } - - // Native classes - for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); - r_result.insert(option.display, option); - } -} - -static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) { +static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) { Variant base = p_base.value; GDScriptParser::DataType base_type = p_base.type; const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; -#define IS_METHOD_SIGNAL(m_method) (m_method == "connect" || m_method == "disconnect" || m_method == "is_connected" || m_method == "emit_signal") - - while (base_type.has_type) { + while (base_type.is_set() && !base_type.is_variant()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { - for (int i = 0; i < base_type.class_type->static_functions.size(); i++) { - if (base_type.class_type->static_functions[i]->name == p_method) { - r_arghint = _make_arguments_hint(base_type.class_type->static_functions[i], p_argidx); - return; - } - } - for (int i = 0; i < base_type.class_type->functions.size(); i++) { - if (base_type.class_type->functions[i]->name == p_method) { - r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx); - return; - } - } + if (base_type.class_type->has_member(p_method)) { + const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method); - if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) { - for (int i = 0; i < base_type.class_type->_signals.size(); i++) { - ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL); - option.insert_text = quote_style + option.display + quote_style; - r_result.insert(option.display, option); + if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { + r_arghint = _make_arguments_hint(member.function, p_argidx); + return; } } base_type = base_type.class_type->base_type; } break; - case GDScriptParser::DataType::GDSCRIPT: { - Ref<GDScript> gds = base_type.script_type; - if (gds.is_valid()) { - if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) { - List<MethodInfo> signals; - gds->get_script_signal_list(&signals); - for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); - option.insert_text = quote_style + option.display + quote_style; - r_result.insert(option.display, option); - } - } - Ref<GDScript> base_script = gds->get_base_script(); - if (base_script.is_valid()) { - base_type.script_type = base_script; - } else { - base_type.kind = GDScriptParser::DataType::NATIVE; - base_type.native_type = gds->get_instance_base_type(); - } - } else { - return; - } - } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = base_type.native_type; + StringName class_name = _get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { - class_name = String("_") + class_name; - if (!ClassDB::class_exists(class_name)) { - base_type.has_type = false; - break; - } + base_type.kind = GDScriptParser::DataType::UNRESOLVED; + break; } - List<MethodInfo> methods; - ClassDB::get_method_list(class_name, &methods); - ClassDB::get_virtual_methods(class_name, &methods); + MethodInfo info; int method_args = 0; - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name == p_method) { - method_args = E->get().arguments.size(); - if (base.get_type() == Variant::OBJECT) { - Object *obj = base.operator Object *(); - if (obj) { - List<String> options; - obj->get_argument_options(p_method, p_argidx, &options); - for (List<String>::Element *F = options.front(); F; F = F->next()) { - ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION); - r_result.insert(option.display, option); - } + if (ClassDB::get_method_info(class_name, p_method, &info)) { + method_args = info.arguments.size(); + if (base.get_type() == Variant::OBJECT) { + Object *obj = base.operator Object *(); + if (obj) { + List<String> options; + obj->get_argument_options(p_method, p_argidx, &options); + for (List<String>::Element *F = options.front(); F; F = F->next()) { + ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION); + r_result.insert(option.display, option); } } + } - if (p_argidx < method_args) { - PropertyInfo arg_info = E->get().arguments[p_argidx]; - if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { - _find_enumeration_candidates(arg_info.class_name, r_result); - } + if (p_argidx < method_args) { + PropertyInfo arg_info = info.arguments[p_argidx]; + if (arg_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + _find_enumeration_candidates(p_context, arg_info.class_name, r_result); } - - r_arghint = _make_arguments_hint(E->get(), p_argidx); - break; } - } - if (IS_METHOD_SIGNAL(p_method) && p_argidx == 0) { - List<MethodInfo> signals; - ClassDB::get_signal_list(class_name, &signals); - for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); - option.insert_text = quote_style + option.display + quote_style; - r_result.insert(option.display, option); - } + r_arghint = _make_arguments_hint(info, p_argidx); + return; } -#undef IS_METHOD_SIGNAL if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) { // Get autoloads @@ -2374,12 +2297,12 @@ 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) { Callable::CallError err; - base = Variant::construct(base_type.builtin_type, nullptr, 0, err); + Variant::construct(base_type.builtin_type, base, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return; } @@ -2394,295 +2317,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 = call->get_callee_type(); GDScriptCompletionIdentifier connect_base; - if (op->op != GDScriptParser::OperatorNode::OP_CALL && op->op != GDScriptParser::OperatorNode::OP_PARENT_CALL) { - return; - } - - if (!op->arguments.size()) { + if (GDScriptUtilityFunctions::function_exists(call->function_name)) { + MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name); + r_arghint = _make_arguments_hint(info, p_argidx); return; - } - - if (op->op == GDScriptParser::OperatorNode::OP_CALL) { - if (op->arguments[0]->type == GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION) { - // Complete built-in function - const GDScriptParser::BuiltInFunctionNode *fn = static_cast<const GDScriptParser::BuiltInFunctionNode *>(op->arguments[0]); - MethodInfo mi = GDScriptFunctions::get_info(fn->function); - - if ((mi.name == "load" || mi.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { - _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); - } - - r_arghint = _make_arguments_hint(mi, p_argidx); - return; - - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_TYPE) { - // Complete constructor - const GDScriptParser::TypeNode *tn = static_cast<const GDScriptParser::TypeNode *>(op->arguments[0]); - - List<MethodInfo> constructors; - Variant::get_constructor_list(tn->vtype, &constructors); - - 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++; + } 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; } - return; - } else if (op->arguments[0]->type == GDScriptParser::Node::TYPE_SELF) { - - if (op->arguments.size() < 2 || op->arguments[1]->type != GDScriptParser::Node::TYPE_IDENTIFIER) { - return; - } - - 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); + bool valid = false; + Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E->get(), &valid); + if (valid) { + option.default_value = default_value; + } options.insert(option.display, option); } } break; - 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, 0); + r_forced = true; break; } + if (!type.enumeration.is_empty()) { + _find_enumeration_candidates(completion_context, type.enumeration, options); + r_forced = options.size() > 0; + } else { + _find_identifiers(completion_context, false, options, 0); + r_forced = true; + } + } break; + case GDScriptParser::COMPLETION_METHOD: + is_function = true; + [[fallthrough]]; + case GDScriptParser::COMPLETION_IDENTIFIER: { + _find_identifiers(completion_context, is_function, options, 0); + } 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, 0); + } + } 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, 0); + } 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, 0); + } + 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(); @@ -2690,7 +2626,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) { @@ -2735,245 +2670,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, 0); } break; } @@ -3014,19 +2745,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; @@ -3034,8 +2762,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()) { @@ -3049,12 +2778,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; @@ -3070,8 +2799,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]; } } @@ -3081,45 +2811,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); @@ -3137,17 +2841,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)) { @@ -3187,15 +2888,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); @@ -3206,11 +2903,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; @@ -3226,7 +2923,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); + Variant::construct(base_type.builtin_type, v, NULL, 0, err); if (err.error != Callable::CallError::CALL_OK) { break; } @@ -3249,7 +2946,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; } } @@ -3258,7 +2955,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; @@ -3282,13 +2978,11 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol } } - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - if (GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i)) == p_symbol) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; - r_result.class_name = "@GDScript"; - r_result.class_member = p_symbol; - return OK; - } + if (GDScriptUtilityFunctions::function_exists(p_symbol)) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; + r_result.class_name = "@GDScript"; + r_result.class_member = p_symbol; + return OK; } if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { @@ -3299,26 +2993,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; } @@ -3326,58 +3012,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)) { + 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 = 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) { - 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; } } @@ -3386,40 +3054,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]]; @@ -3446,7 +3101,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; @@ -3466,17 +3121,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; } @@ -3484,19 +3142,16 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol return OK; } } break; - case GDScriptParser::COMPLETION_VIRTUAL_FUNC: { - GDScriptParser::DataType base_type = context._class->base_type; + case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { + GDScriptParser::DataType base_type = context.current_class->base_type; if (_lookup_symbol_from_base(base_type, p_symbol, true, r_result) == OK) { return OK; } } break; - case GDScriptParser::COMPLETION_TYPE_HINT: { - - GDScriptParser::DataType base_type = context._class->base_type; - base_type.has_type = true; - base_type.kind = GDScriptParser::DataType::CLASS; - base_type.class_type = const_cast<GDScriptParser::ClassNode *>(context._class); + case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: + case GDScriptParser::COMPLETION_TYPE_NAME: { + GDScriptParser::DataType base_type = context.current_class->get_datatype(); if (_lookup_symbol_from_base(base_type, p_symbol, false, r_result) == OK) { return OK; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 44640411bb..2171426e6f 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -30,1637 +30,31 @@ #include "gdscript_function.h" -#include "core/os/os.h" #include "gdscript.h" -#include "gdscript_functions.h" - -Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { - - int address = p_address & ADDR_MASK; - - //sequential table (jump table generated by compiler) - switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) { - - case ADDR_TYPE_SELF: { -#ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access self without instance."; - return nullptr; - } -#endif - return &self; - } break; - case ADDR_TYPE_CLASS: { - - return &static_ref; - } break; - case ADDR_TYPE_MEMBER: { -#ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access member without instance."; - return nullptr; - } -#endif - //member indexing is O(1) - return &p_instance->members.write[address]; - } break; - case ADDR_TYPE_CLASS_CONSTANT: { - - //todo change to index! - GDScript *s = p_script; -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); -#endif - const StringName *sn = &_global_names_ptr[address]; - - while (s) { - GDScript *o = s; - while (o) { - - Map<StringName, Variant>::Element *E = o->constants.find(*sn); - if (E) { - return &E->get(); - } - o = o->_owner; - } - s = s->_base; - } - - ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug."); - } break; - case ADDR_TYPE_LOCAL_CONSTANT: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _constant_count, nullptr); -#endif - return &_constants_ptr[address]; - } break; - case ADDR_TYPE_STACK: - case ADDR_TYPE_STACK_VARIABLE: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _stack_size, nullptr); -#endif - return &p_stack[address]; - } break; - case ADDR_TYPE_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr); -#endif - return &GDScriptLanguage::get_singleton()->get_global_array()[address]; - } break; -#ifdef TOOLS_ENABLED - case ADDR_TYPE_NAMED_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _named_globals_count, nullptr); -#endif - StringName id = _named_globals_ptr[address]; - - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { - return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; - } else { - r_error = "Autoload singleton '" + String(id) + "' has been removed."; - return nullptr; - } - } break; -#endif - case ADDR_TYPE_NIL: { - return &nil; - } break; - } - - ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); - return nullptr; -} - -#ifdef DEBUG_ENABLED -static String _get_var_type(const Variant *p_var) { - - String basestr; - - if (p_var->get_type() == Variant::OBJECT) { - bool was_freed; - Object *bobj = p_var->get_validated_object_with_check(was_freed); - if (!bobj) { - if (was_freed) { - basestr = "null instance"; - } else { - basestr = "previously freed"; - } - } else { - if (bobj->get_script_instance()) - basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; - else - basestr = bobj->get_class(); - } - - } else { - basestr = Variant::get_type_name(p_var->get_type()); - } - - return basestr; -} -#endif // DEBUG_ENABLED - -String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { - - String err_text; - - if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - int errorarg = p_err.argument; - // Handle the Object to Object case separately as we don't have further class details. -#ifdef DEBUG_ENABLED - if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { - err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; - } else -#endif // DEBUG_ENABLED - { - err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; - } - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - err_text = "Invalid call. Nonexistent " + p_where + "."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { - err_text = "Attempt to call " + p_where + " on a null instance."; - } else { - err_text = "Bug, call error: #" + itos(p_err.error); - } - - return err_text; -} - -#if defined(__GNUC__) -#define OPCODES_TABLE \ - static const void *switch_table_ops[] = { \ - &&OPCODE_OPERATOR, \ - &&OPCODE_EXTENDS_TEST, \ - &&OPCODE_IS_BUILTIN, \ - &&OPCODE_SET, \ - &&OPCODE_GET, \ - &&OPCODE_SET_NAMED, \ - &&OPCODE_GET_NAMED, \ - &&OPCODE_SET_MEMBER, \ - &&OPCODE_GET_MEMBER, \ - &&OPCODE_ASSIGN, \ - &&OPCODE_ASSIGN_TRUE, \ - &&OPCODE_ASSIGN_FALSE, \ - &&OPCODE_ASSIGN_TYPED_BUILTIN, \ - &&OPCODE_ASSIGN_TYPED_NATIVE, \ - &&OPCODE_ASSIGN_TYPED_SCRIPT, \ - &&OPCODE_CAST_TO_BUILTIN, \ - &&OPCODE_CAST_TO_NATIVE, \ - &&OPCODE_CAST_TO_SCRIPT, \ - &&OPCODE_CONSTRUCT, \ - &&OPCODE_CONSTRUCT_ARRAY, \ - &&OPCODE_CONSTRUCT_DICTIONARY, \ - &&OPCODE_CALL, \ - &&OPCODE_CALL_RETURN, \ - &&OPCODE_CALL_BUILT_IN, \ - &&OPCODE_CALL_SELF, \ - &&OPCODE_CALL_SELF_BASE, \ - &&OPCODE_YIELD, \ - &&OPCODE_YIELD_SIGNAL, \ - &&OPCODE_YIELD_RESUME, \ - &&OPCODE_JUMP, \ - &&OPCODE_JUMP_IF, \ - &&OPCODE_JUMP_IF_NOT, \ - &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ - &&OPCODE_RETURN, \ - &&OPCODE_ITERATE_BEGIN, \ - &&OPCODE_ITERATE, \ - &&OPCODE_ASSERT, \ - &&OPCODE_BREAKPOINT, \ - &&OPCODE_LINE, \ - &&OPCODE_END \ - }; - -#define OPCODE(m_op) \ - m_op: -#define OPCODE_WHILE(m_test) -#define OPCODES_END \ - OPSEXIT: -#define OPCODES_OUT \ - OPSOUT: -#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]] -#define OPCODE_SWITCH(m_test) DISPATCH_OPCODE; -#define OPCODE_BREAK goto OPSEXIT -#define OPCODE_OUT goto OPSOUT -#else -#define OPCODES_TABLE -#define OPCODE(m_op) case m_op: -#define OPCODE_WHILE(m_test) while (m_test) -#define OPCODES_END -#define OPCODES_OUT -#define DISPATCH_OPCODE continue -#define OPCODE_SWITCH(m_test) switch (m_test) -#define OPCODE_BREAK break -#define OPCODE_OUT break -#endif - -Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) { - - OPCODES_TABLE; - - if (!_code_ptr) { - - return Variant(); - } - - r_err.error = Callable::CallError::CALL_OK; - - Variant self; - Variant static_ref; - Variant retvalue; - Variant *stack = nullptr; - Variant **call_args; - int defarg = 0; - -#ifdef DEBUG_ENABLED - - //GDScriptLanguage::get_singleton()->calls++; - -#endif - - uint32_t alloca_size = 0; - GDScript *script; - int ip = 0; - int line = _initial_line; - - if (p_state) { - //use existing (supplied) state (yielded) - 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 = static_cast<GDScript *>(ObjectDB::get_instance(p_state->script_id)); - p_instance = p_state->instance_id.is_valid() ? static_cast<GDScriptInstance *>(ObjectDB::get_instance(p_state->instance_id)->get_script_instance()) : nullptr; - defarg = p_state->defarg; - self = p_state->self; - - } else { - - if (p_argcount != _argument_count) { - - if (p_argcount > _argument_count) { - - r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_err.argument = _argument_count; - - return Variant(); - } else if (p_argcount < _argument_count - _default_arg_count) { - - r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_err.argument = _argument_count - _default_arg_count; - return Variant(); - } else { - - defarg = _argument_count - p_argcount; - } - } - - alloca_size = sizeof(Variant *) * _call_size + sizeof(Variant) * _stack_size; - - if (alloca_size) { - - uint8_t *aptr = (uint8_t *)alloca(alloca_size); - - if (_stack_size) { - - stack = (Variant *)aptr; - for (int i = 0; i < p_argcount; i++) { - if (!argument_types[i].has_type) { - memnew_placement(&stack[i], Variant(*p_args[i])); - continue; - } - - if (!argument_types[i].is_type(*p_args[i], true)) { - r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_err.argument = i; - r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; - return Variant(); - } - if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err); - memnew_placement(&stack[i], Variant(arg)); - } else { - memnew_placement(&stack[i], Variant(*p_args[i])); - } - } - for (int i = p_argcount; i < _stack_size; i++) { - memnew_placement(&stack[i], Variant); - } - } else { - stack = nullptr; - } - - if (_call_size) { - - call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; - } else { - - call_args = nullptr; - } - - } else { - stack = nullptr; - call_args = nullptr; - } - - if (p_instance) { - if (p_instance->base_ref && static_cast<Reference *>(p_instance->owner)->is_referenced()) { - - self = REF(static_cast<Reference *>(p_instance->owner)); - } else { - self = p_instance->owner; - } - script = p_instance->script.ptr(); - } else { - script = _script; - } - } - - String err_text; - -#ifdef DEBUG_ENABLED - - if (EngineDebugger::is_active()) - GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line); - -#define GD_ERR_BREAK(m_cond) \ - { \ - if (unlikely(m_cond)) { \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \ - OPCODE_BREAK; \ - } \ - } - -#define CHECK_SPACE(m_space) \ - GD_ERR_BREAK((ip + m_space) > _code_size) - -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \ - if (unlikely(!m_v)) \ - OPCODE_BREAK; - -#else -#define GD_ERR_BREAK(m_cond) -#define CHECK_SPACE(m_space) -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); - -#endif - -#ifdef DEBUG_ENABLED - - uint64_t function_start_time = 0; - uint64_t function_call_time = 0; - - if (GDScriptLanguage::get_singleton()->profiling) { - function_start_time = OS::get_singleton()->get_ticks_usec(); - function_call_time = 0; - profile.call_count++; - profile.frame_call_count++; - } - bool exit_ok = false; - bool yielded = false; -#endif - -#ifdef DEBUG_ENABLED - OPCODE_WHILE(ip < _code_size) { - int last_opcode = _code_ptr[ip]; -#else - OPCODE_WHILE(true) { -#endif - - OPCODE_SWITCH(_code_ptr[ip]) { - - OPCODE(OPCODE_OPERATOR) { - - CHECK_SPACE(5); - - bool valid; - Variant::Operator op = (Variant::Operator)_code_ptr[ip + 1]; - GD_ERR_BREAK(op >= Variant::OP_MAX); - - GET_VARIANT_PTR(a, 2); - GET_VARIANT_PTR(b, 3); - GET_VARIANT_PTR(dst, 4); - -#ifdef DEBUG_ENABLED - - Variant ret; - Variant::evaluate(op, *a, *b, ret, valid); -#else - Variant::evaluate(op, *a, *b, *dst, valid); -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - - if (ret.get_type() == Variant::STRING) { - //return a string when invalid with the error - err_text = ret; - err_text += " in operator '" + Variant::get_operator_name(op) + "'."; - } else { - err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'."; - } - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 5; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_EXTENDS_TEST) { - - CHECK_SPACE(4); - - GET_VARIANT_PTR(a, 1); - GET_VARIANT_PTR(b, 2); - GET_VARIANT_PTR(dst, 3); - -#ifdef DEBUG_ENABLED - if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { - - err_text = "Right operand of 'is' is not a class."; - OPCODE_BREAK; - } -#endif - - bool extends_ok = false; - if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) { - -#ifdef DEBUG_ENABLED - bool was_freed; - Object *obj_A = a->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Left operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } - - Object *obj_B = b->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Right operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } -#else - - Object *obj_A = *a; - Object *obj_B = *b; -#endif // DEBUG_ENABLED - - GDScript *scr_B = Object::cast_to<GDScript>(obj_B); - - if (scr_B) { - //if B is a script, the only valid condition is that A has an instance which inherits from the script - //in other situation, this shoul return false. - - if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { - - GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); - //bool found=false; - while (cmp) { - - if (cmp == scr_B) { - //inherits from script, all ok - extends_ok = true; - break; - } - - cmp = cmp->_base; - } - } - - } else { - - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); - -#ifdef DEBUG_ENABLED - if (!nc) { - - err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; - OPCODE_BREAK; - } -#endif - extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); - } - } - - *dst = extends_ok; - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_IS_BUILTIN) { - - CHECK_SPACE(4); - - GET_VARIANT_PTR(value, 1); - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 2]; - GET_VARIANT_PTR(dst, 3); - - GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); - - *dst = value->get_type() == var_type; - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET) { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(index, 2); - GET_VARIANT_PTR(value, 3); - - bool valid; - dst->set(*index, *value, &valid); - -#ifdef DEBUG_ENABLED - if (!valid) { - String v = index->operator String(); - if (v != "") { - v = "'" + v + "'"; - } else { - v = "of type '" + _get_var_type(index) + "'"; - } - err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; - OPCODE_BREAK; - } -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET) { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(src, 1); - GET_VARIANT_PTR(index, 2); - GET_VARIANT_PTR(dst, 3); - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get(*index, &valid); -#else - *dst = src->get(*index, &valid); - -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - String v = index->operator String(); - if (v != "") { - v = "'" + v + "'"; - } else { - v = "of type '" + _get_var_type(index) + "'"; - } - err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET_NAMED) { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(value, 3); - - int indexname = _code_ptr[ip + 2]; - - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; - dst->set_named(*index, *value, &valid); - -#ifdef DEBUG_ENABLED - if (!valid) { - String err_type; - err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; - OPCODE_BREAK; - } -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET_NAMED) { - - CHECK_SPACE(4); - - GET_VARIANT_PTR(src, 1); - GET_VARIANT_PTR(dst, 3); - - int indexname = _code_ptr[ip + 2]; - - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get_named(*index, &valid); - -#else - *dst = src->get_named(*index, &valid); -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - if (src->has_method(*index)) { - err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' or funcref(obj, \"" + index->operator String() + "\") ?"; - } else { - err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "')."; - } - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET_MEMBER) { - - CHECK_SPACE(3); - int indexname = _code_ptr[ip + 1]; - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - GET_VARIANT_PTR(src, 2); - - bool valid; -#ifndef DEBUG_ENABLED - ClassDB::set_property(p_instance->owner, *index, *src, &valid); -#else - bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid); - if (!ok) { - err_text = "Internal error setting property: " + String(*index); - OPCODE_BREAK; - } else if (!valid) { - err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + "."; - OPCODE_BREAK; - } -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET_MEMBER) { - - CHECK_SPACE(3); - int indexname = _code_ptr[ip + 1]; - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - GET_VARIANT_PTR(dst, 2); - -#ifndef DEBUG_ENABLED - ClassDB::get_property(p_instance->owner, *index, *dst); -#else - bool ok = ClassDB::get_property(p_instance->owner, *index, *dst); - if (!ok) { - err_text = "Internal error getting property: " + String(*index); - OPCODE_BREAK; - } -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN) { - - CHECK_SPACE(3); - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(src, 2); - - *dst = *src; - - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TRUE) { - - CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 1); - - *dst = true; - - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_FALSE) { - - CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 1); - - *dst = false; - - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { - - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 1]; - GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); - - if (src->get_type() != var_type) { -#ifdef DEBUG_ENABLED - if (Variant::can_convert_strict(src->get_type(), var_type)) { -#endif // DEBUG_ENABLED - Callable::CallError ce; - *dst = Variant::construct(var_type, const_cast<const Variant **>(&src), 1, ce); - } else { -#ifdef DEBUG_ENABLED - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; - OPCODE_BREAK; - } - } else { -#endif // DEBUG_ENABLED - *dst = *src; - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { - - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(type, 1); - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); - GD_ERR_BREAK(!nc); - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + nc->get_name() + "'."; - OPCODE_BREAK; - } - Object *src_obj = src->operator Object *(); - - if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { - err_text = "Trying to assign value of type '" + src_obj->get_class_name() + - "' to a variable of type '" + nc->get_name() + "'."; - OPCODE_BREAK; - } -#endif // DEBUG_ENABLED - *dst = *src; - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { - - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(type, 1); - Script *base_type = Object::cast_to<Script>(type->operator Object *()); - - GD_ERR_BREAK(!base_type); - - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - - if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { - - ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); - if (!scr_inst) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - - Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); - bool valid = false; - - while (src_type) { - if (src_type == base_type) { - valid = true; - break; - } - src_type = src_type->get_base_script().ptr(); - } - - if (!valid) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - } -#endif // DEBUG_ENABLED - - *dst = *src; - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_BUILTIN) { - - CHECK_SPACE(4); - Variant::Type to_type = (Variant::Type)_code_ptr[ip + 1]; - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); - - Callable::CallError err; - *dst = Variant::construct(to_type, (const Variant **)&src, 1, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'."; - OPCODE_BREAK; - } -#endif - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_NATIVE) { - - CHECK_SPACE(4); - GET_VARIANT_PTR(to_type, 1); - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); - GD_ERR_BREAK(!nc); - -#ifdef DEBUG_ENABLED - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Invalid cast: can't convert a non-object value to an object type."; - OPCODE_BREAK; - } -#endif - Object *src_obj = src->operator Object *(); - - if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { - *dst = Variant(); // invalid cast, assign NULL - } else { - *dst = *src; - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_SCRIPT) { - - CHECK_SPACE(4); - GET_VARIANT_PTR(to_type, 1); - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); - - GD_ERR_BREAK(!base_type); - -#ifdef DEBUG_ENABLED - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } -#endif - - bool valid = false; - - if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { - - ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); - - if (scr_inst) { - - Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); - - while (src_type) { - if (src_type == base_type) { - valid = true; - break; - } - src_type = src_type->get_base_script().ptr(); - } - } - } - - if (valid) { - *dst = *src; // Valid cast, copy the source object - } else { - *dst = Variant(); // invalid cast, assign NULL - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT) { - - CHECK_SPACE(2); - Variant::Type t = Variant::Type(_code_ptr[ip + 1]); - int argc = _code_ptr[ip + 2]; - CHECK_SPACE(argc + 2); - Variant **argptrs = call_args; - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, 3 + i); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, 3 + argc); - Callable::CallError err; - *dst = Variant::construct(t, (const Variant **)argptrs, argc, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - - err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs); - OPCODE_BREAK; - } -#endif - - ip += 4 + argc; - //construct a basic type - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT_ARRAY) { - - CHECK_SPACE(1); - int argc = _code_ptr[ip + 1]; - Array array; //arrays are always shared - array.resize(argc); - CHECK_SPACE(argc + 2); - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, 2 + i); - array[i] = *v; - } - - GET_VARIANT_PTR(dst, 2 + argc); - - *dst = array; - - ip += 3 + argc; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { - - CHECK_SPACE(1); - int argc = _code_ptr[ip + 1]; - Dictionary dict; //arrays are always shared - - CHECK_SPACE(argc * 2 + 2); - - for (int i = 0; i < argc; i++) { - - GET_VARIANT_PTR(k, 2 + i * 2 + 0); - GET_VARIANT_PTR(v, 2 + i * 2 + 1); - dict[*k] = *v; - } - - GET_VARIANT_PTR(dst, 2 + argc * 2); - - *dst = dict; - - ip += 3 + argc * 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_RETURN) - OPCODE(OPCODE_CALL) { - - CHECK_SPACE(4); - bool call_ret = _code_ptr[ip] == OPCODE_CALL_RETURN; - - int argc = _code_ptr[ip + 1]; - GET_VARIANT_PTR(base, 2); - int nameg = _code_ptr[ip + 3]; - - GD_ERR_BREAK(nameg < 0 || nameg >= _global_names_count); - const StringName *methodname = &_global_names_ptr[nameg]; - - GD_ERR_BREAK(argc < 0); - ip += 4; - CHECK_SPACE(argc + 1); - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i); - argptrs[i] = v; - } - -#ifdef DEBUG_ENABLED - uint64_t call_time = 0; - - if (GDScriptLanguage::get_singleton()->profiling) { - call_time = OS::get_singleton()->get_ticks_usec(); - } - -#endif - Callable::CallError err; - if (call_ret) { - - GET_VARIANT_PTR(ret, argc); - base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err); - } else { - - base->call_ptr(*methodname, (const Variant **)argptrs, argc, nullptr, err); - } -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->profiling) { - function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; - } - - if (err.error != Callable::CallError::CALL_OK) { - - String methodstr = *methodname; - String basestr = _get_var_type(base); - - if (methodstr == "call") { - if (argc >= 1) { - methodstr = String(*argptrs[0]) + " (via call)"; - if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - err.argument += 1; - } - } - } else if (methodstr == "free") { - - if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - - if (base->is_ref()) { - err_text = "Attempted to free a reference."; - OPCODE_BREAK; - } else if (base->get_type() == Variant::OBJECT) { - - err_text = "Attempted to free a locked object (calling or emitting)."; - OPCODE_BREAK; - } - } - } - err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); - OPCODE_BREAK; - } -#endif - - //_call_func(nullptr,base,*methodname,ip,argc,p_instance,stack); - ip += argc + 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_BUILT_IN) { - - CHECK_SPACE(4); - - GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 1]); - int argc = _code_ptr[ip + 2]; - GD_ERR_BREAK(argc < 0); - - ip += 3; - CHECK_SPACE(argc + 1); - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, argc); - - Callable::CallError err; - - GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - - String methodstr = GDScriptFunctions::get_func_name(func); - if (dst->get_type() == Variant::STRING) { - //call provided error string - err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst); - } else { - err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs); - } - OPCODE_BREAK; - } -#endif - ip += argc + 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_SELF) { - - OPCODE_BREAK; - } - - OPCODE(OPCODE_CALL_SELF_BASE) { - - CHECK_SPACE(2); - int self_fun = _code_ptr[ip + 1]; - -#ifdef DEBUG_ENABLED - if (self_fun < 0 || self_fun >= _global_names_count) { - - err_text = "compiler bug, function name not found"; - OPCODE_BREAK; - } -#endif - const StringName *methodname = &_global_names_ptr[self_fun]; - - int argc = _code_ptr[ip + 2]; - - CHECK_SPACE(2 + argc + 1); - - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i + 3); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, argc + 3); - - const GDScript *gds = _script; - - const Map<StringName, GDScriptFunction *>::Element *E = nullptr; - while (gds->base.ptr()) { - gds = gds->base.ptr(); - E = gds->member_functions.find(*methodname); - if (E) - break; - } - - Callable::CallError err; - - if (E) { - - *dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err); - } else if (gds->native.ptr()) { - - if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { - - MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname); - if (!mb) { - err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - } else { - *dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err); - } - } else { - err.error = Callable::CallError::CALL_OK; - } - } else { - - if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { - err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - } else { - err.error = Callable::CallError::CALL_OK; - } - } - - if (err.error != Callable::CallError::CALL_OK) { - - String methodstr = *methodname; - err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); - - OPCODE_BREAK; - } - - ip += 4 + argc; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_YIELD) - OPCODE(OPCODE_YIELD_SIGNAL) { - - int ipofs = 1; - if (_code_ptr[ip] == OPCODE_YIELD_SIGNAL) { - CHECK_SPACE(4); - ipofs += 2; - } else { - CHECK_SPACE(2); - } - - 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_id = script->get_instance_id(); -#ifdef DEBUG_ENABLED - gdfs->state.script_path = _script->get_path(); -#endif - gdfs->state.instance_id = (p_instance && p_instance->get_owner()) ? p_instance->get_owner()->get_instance_id() : ObjectID(); - gdfs->state.defarg = defarg; - 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); - -#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 - -#ifdef DEBUG_ENABLED - bool was_freed; - Object *obj = argobj->get_validated_object_with_check(was_freed); - String signal = argname->operator String(); - - if (was_freed) { - err_text = "First argument of yield() is a previously freed instance."; - OPCODE_BREAK; - } - - if (!obj) { - err_text = "First argument of yield() is null."; - OPCODE_BREAK; - } - if (signal.length() == 0) { - - err_text = "Second argument of yield() is an empty string (for signal name)."; - OPCODE_BREAK; - } - - Error err = obj->connect_compat(signal, gdfs.ptr(), "_signal_callback", varray(gdfs), Object::CONNECT_ONESHOT); - if (err != OK) { - err_text = "Error connecting to signal: " + signal + " during yield()."; - 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; -#endif - OPCODE_BREAK; - } - - OPCODE(OPCODE_YIELD_RESUME) { - - CHECK_SPACE(2); -#ifdef DEBUG_ENABLED - if (!p_state) { - err_text = ("Invalid Resume (bug?)"); - OPCODE_BREAK; - } -#endif - GET_VARIANT_PTR(result, 1); - *result = p_state->result; - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP) { - - CHECK_SPACE(2); - int to = _code_ptr[ip + 1]; - - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_IF) { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(test, 1); - - bool result = test->booleanize(); - - if (result) { - int to = _code_ptr[ip + 2]; - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } else { - ip += 3; - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_IF_NOT) { - - CHECK_SPACE(3); - - GET_VARIANT_PTR(test, 1); - - bool result = test->booleanize(); - - if (!result) { - int to = _code_ptr[ip + 2]; - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } else { - ip += 3; - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) { - - CHECK_SPACE(2); - ip = _default_arg_ptr[defarg]; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_RETURN) { - - CHECK_SPACE(2); - GET_VARIANT_PTR(r, 1); - retvalue = *r; -#ifdef DEBUG_ENABLED - exit_ok = true; -#endif - OPCODE_BREAK; - } - - OPCODE(OPCODE_ITERATE_BEGIN) { - - CHECK_SPACE(8); //space for this a regular iterate - - GET_VARIANT_PTR(counter, 1); - GET_VARIANT_PTR(container, 2); - - bool valid; - if (!container->iter_init(*counter, valid)) { -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'."; - OPCODE_BREAK; - } -#endif - int jumpto = _code_ptr[ip + 3]; - GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); - ip = jumpto; - } else { - GET_VARIANT_PTR(iterator, 4); - - *iterator = container->iter_get(*counter, valid); -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'."; - OPCODE_BREAK; - } -#endif - ip += 5; //skip regular iterate which is always next - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ITERATE) { - - CHECK_SPACE(4); - - GET_VARIANT_PTR(counter, 1); - GET_VARIANT_PTR(container, 2); - - bool valid; - if (!container->iter_next(*counter, valid)) { -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; - OPCODE_BREAK; - } -#endif - int jumpto = _code_ptr[ip + 3]; - GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); - ip = jumpto; - } else { - GET_VARIANT_PTR(iterator, 4); - - *iterator = container->iter_get(*counter, valid); -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; - OPCODE_BREAK; - } -#endif - ip += 5; //loop again - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSERT) { - CHECK_SPACE(3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(test, 1); - GET_VARIANT_PTR(message, 2); - bool result = test->booleanize(); - - if (!result) { - const String &message_str = *message; - if (message_str.empty()) { - err_text = "Assertion failed."; - } else { - err_text = "Assertion failed: " + message_str; - } - OPCODE_BREAK; - } - -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_BREAKPOINT) { -#ifdef DEBUG_ENABLED - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true); - } -#endif - ip += 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_LINE) { - CHECK_SPACE(2); - - line = _code_ptr[ip + 1]; - ip += 2; - - if (EngineDebugger::is_active()) { - // line - bool do_break = false; - - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { - - if (EngineDebugger::get_script_debugger()->get_depth() <= 0) - EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1); - if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) - do_break = true; - } - - if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) - do_break = true; - - if (do_break) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); - } - - EngineDebugger::get_singleton()->line_poll(); - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_END) { -#ifdef DEBUG_ENABLED - exit_ok = true; -#endif - OPCODE_BREAK; - } - -#if 0 // Enable for debugging. - default: { - err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip); - OPCODE_BREAK; - } -#endif - } - - OPCODES_END -#ifdef DEBUG_ENABLED - if (exit_ok) - OPCODE_OUT; - //error - // function, file, line, error, explanation - String err_file; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") - err_file = p_instance->script->path; - else if (script) - err_file = script->path; - if (err_file == "") - err_file = "<built-in>"; - String err_func = name; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->name != "") - err_func = p_instance->script->name + "." + err_func; - int err_line = line; - if (err_text == "") { - err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please)."; - } - - if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) { - // debugger break did not happen - - _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT); - } - -#endif - OPCODE_OUT; - } - - OPCODES_OUT -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->profiling) { - uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; - profile.total_time += time_taken; - profile.self_time += time_taken - function_call_time; - profile.frame_total_time += time_taken; - profile.frame_self_time += time_taken - function_call_time; - GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; - } - - // Check if this is the last time the function is resuming from yield - // Will be true if never yielded 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()) - GDScriptLanguage::get_singleton()->exit_function(); -#endif - - if (_stack_size) { - //free stack - for (int i = 0; i < _stack_size; i++) - stack[i].~Variant(); - } - -#ifdef DEBUG_ENABLED - } -#endif - - return retvalue; -} const int *GDScriptFunction::get_code() const { - return _code_ptr; } -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]; } @@ -1675,45 +69,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++; @@ -1724,18 +111,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.is_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; @@ -1746,7 +132,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; @@ -1754,32 +139,13 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String } } -GDScriptFunction::GDScriptFunction() : - function_list(this) { - - _stack_size = 0; - _call_size = 0; - rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; +GDScriptFunction::GDScriptFunction() { name = "<anonymous>"; #ifdef DEBUG_ENABLED - _func_cname = nullptr; - { MutexLock lock(GDScriptLanguage::get_singleton()->lock); - GDScriptLanguage::get_singleton()->function_list.add(&function_list); } - - profile.call_count = 0; - profile.self_time = 0; - profile.total_time = 0; - profile.frame_call_count = 0; - profile.frame_self_time = 0; - profile.frame_total_time = 0; - profile.last_frame_call_count = 0; - profile.last_frame_self_time = 0; - profile.last_frame_total_time = 0; - #endif } @@ -1795,7 +161,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; @@ -1828,17 +193,19 @@ 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? (Otherwise script is valid for sure, because the instance has a ref to the script) - 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; } - // Script gone? (Static method, so there's no instance whose ref to the script can ensure it's valid) - if (!ObjectDB::get_instance(state.script_id)) { + // Class instance gone? (if not static function) + if (state.instance && !instances_list.in_list()) { return false; } } @@ -1847,21 +214,27 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const { } 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_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 (!ObjectDB::get_instance(state.script_id)) { + } + if (state.instance && !instances_list.in_list()) { #ifdef DEBUG_ENABLED - ERR_FAIL_V_MSG(Variant(), "Resumed function '" + String(function->get_name()) + "()' after yield, but script is gone. At script: " + state.script_path + ":" + itos(state.line)); + ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line)); #else - return Variant(); + 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; @@ -1871,7 +244,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) { @@ -1891,13 +264,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 } @@ -1905,8 +273,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")); @@ -1914,18 +291,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 9d8e23d994..6c791836b9 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,32 +31,39 @@ #ifndef GDSCRIPT_FUNCTION_H #define GDSCRIPT_FUNCTION_H +#include "core/object/reference.h" +#include "core/object/script_language.h" #include "core/os/thread.h" -#include "core/pair.h" -#include "core/reference.h" -#include "core/script_language.h" -#include "core/self_list.h" -#include "core/string_name.h" -#include "core/variant.h" +#include "core/string/string_name.h" +#include "core/templates/pair.h" +#include "core/templates/self_list.h" +#include "core/variant/variant.h" +#include "gdscript_utility_functions.h" 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; + Script *script_type = nullptr; + Ref<Script> script_type_ref; bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const { - if (!has_type) return true; // Can't type check + if (!has_type) { + return true; // Can't type check + } switch (kind) { case UNINITIALIZED: @@ -146,22 +153,26 @@ struct GDScriptDataType { return info; } - GDScriptDataType() : - has_type(false), - kind(UNINITIALIZED), - builtin_type(Variant::NIL) {} + GDScriptDataType() {} }; class GDScriptFunction { public: enum Opcode { OPCODE_OPERATOR, + OPCODE_OPERATOR_VALIDATED, OPCODE_EXTENDS_TEST, OPCODE_IS_BUILTIN, - OPCODE_SET, - OPCODE_GET, + OPCODE_SET_KEYED, + OPCODE_SET_KEYED_VALIDATED, + OPCODE_SET_INDEXED_VALIDATED, + OPCODE_GET_KEYED, + OPCODE_GET_KEYED_VALIDATED, + OPCODE_GET_INDEXED_VALIDATED, OPCODE_SET_NAMED, + OPCODE_SET_NAMED_VALIDATED, OPCODE_GET_NAMED, + OPCODE_GET_NAMED_VALIDATED, OPCODE_SET_MEMBER, OPCODE_GET_MEMBER, OPCODE_ASSIGN, @@ -173,24 +184,103 @@ public: OPCODE_CAST_TO_BUILTIN, OPCODE_CAST_TO_NATIVE, OPCODE_CAST_TO_SCRIPT, - OPCODE_CONSTRUCT, //only for basic types!! + OPCODE_CONSTRUCT, // Only for basic types! + OPCODE_CONSTRUCT_VALIDATED, // Only for basic types! OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, - OPCODE_CALL_BUILT_IN, - OPCODE_CALL_SELF, + OPCODE_CALL_ASYNC, + OPCODE_CALL_UTILITY, + OPCODE_CALL_UTILITY_VALIDATED, + OPCODE_CALL_GDSCRIPT_UTILITY, + OPCODE_CALL_BUILTIN_TYPE_VALIDATED, OPCODE_CALL_SELF_BASE, - OPCODE_YIELD, - OPCODE_YIELD_SIGNAL, - OPCODE_YIELD_RESUME, + OPCODE_CALL_METHOD_BIND, + OPCODE_CALL_METHOD_BIND_RET, + // ptrcall have one instruction per return type. + OPCODE_CALL_PTRCALL_NO_RETURN, + OPCODE_CALL_PTRCALL_BOOL, + OPCODE_CALL_PTRCALL_INT, + OPCODE_CALL_PTRCALL_FLOAT, + OPCODE_CALL_PTRCALL_STRING, + OPCODE_CALL_PTRCALL_VECTOR2, + OPCODE_CALL_PTRCALL_VECTOR2I, + OPCODE_CALL_PTRCALL_RECT2, + OPCODE_CALL_PTRCALL_RECT2I, + OPCODE_CALL_PTRCALL_VECTOR3, + OPCODE_CALL_PTRCALL_VECTOR3I, + OPCODE_CALL_PTRCALL_TRANSFORM2D, + OPCODE_CALL_PTRCALL_PLANE, + OPCODE_CALL_PTRCALL_QUAT, + OPCODE_CALL_PTRCALL_AABB, + OPCODE_CALL_PTRCALL_BASIS, + OPCODE_CALL_PTRCALL_TRANSFORM, + OPCODE_CALL_PTRCALL_COLOR, + OPCODE_CALL_PTRCALL_STRING_NAME, + OPCODE_CALL_PTRCALL_NODE_PATH, + OPCODE_CALL_PTRCALL_RID, + OPCODE_CALL_PTRCALL_OBJECT, + OPCODE_CALL_PTRCALL_CALLABLE, + OPCODE_CALL_PTRCALL_SIGNAL, + OPCODE_CALL_PTRCALL_DICTIONARY, + OPCODE_CALL_PTRCALL_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, + OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, + OPCODE_AWAIT, + OPCODE_AWAIT_RESUME, OPCODE_JUMP, OPCODE_JUMP_IF, OPCODE_JUMP_IF_NOT, OPCODE_JUMP_TO_DEF_ARGUMENT, OPCODE_RETURN, OPCODE_ITERATE_BEGIN, + OPCODE_ITERATE_BEGIN_INT, + OPCODE_ITERATE_BEGIN_FLOAT, + OPCODE_ITERATE_BEGIN_VECTOR2, + OPCODE_ITERATE_BEGIN_VECTOR2I, + OPCODE_ITERATE_BEGIN_VECTOR3, + OPCODE_ITERATE_BEGIN_VECTOR3I, + OPCODE_ITERATE_BEGIN_STRING, + OPCODE_ITERATE_BEGIN_DICTIONARY, + OPCODE_ITERATE_BEGIN_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, + OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, + OPCODE_ITERATE_BEGIN_OBJECT, OPCODE_ITERATE, + OPCODE_ITERATE_INT, + OPCODE_ITERATE_FLOAT, + OPCODE_ITERATE_VECTOR2, + OPCODE_ITERATE_VECTOR2I, + OPCODE_ITERATE_VECTOR3, + OPCODE_ITERATE_VECTOR3I, + OPCODE_ITERATE_STRING, + OPCODE_ITERATE_DICTIONARY, + OPCODE_ITERATE_ARRAY, + OPCODE_ITERATE_PACKED_BYTE_ARRAY, + OPCODE_ITERATE_PACKED_INT32_ARRAY, + OPCODE_ITERATE_PACKED_INT64_ARRAY, + OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, + OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, + OPCODE_ITERATE_PACKED_STRING_ARRAY, + OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, + OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, + OPCODE_ITERATE_PACKED_COLOR_ARRAY, + OPCODE_ITERATE_OBJECT, OPCODE_ASSERT, OPCODE_BREAKPOINT, OPCODE_LINE, @@ -213,8 +303,13 @@ public: ADDR_TYPE_NIL = 9 }; - struct StackDebug { + enum Instruction { + INSTR_BITS = 20, + INSTR_MASK = ((1 << INSTR_BITS) - 1), + INSTR_ARGS_MASK = ~INSTR_MASK, + }; + struct StackDebug { int line; int pos; bool added; @@ -223,44 +318,77 @@ public: private: friend class GDScriptCompiler; + friend class GDScriptByteCodeGenerator; StringName source; mutable Variant nil; - mutable Variant *_constants_ptr; - int _constant_count; - const StringName *_global_names_ptr; - int _global_names_count; -#ifdef TOOLS_ENABLED - const StringName *_named_globals_ptr; - int _named_globals_count; -#endif - const int *_default_arg_ptr; - int _default_arg_count; - const int *_code_ptr; - int _code_size; - int _argument_count; - int _stack_size; - int _call_size; - int _initial_line; - bool _static; - MultiplayerAPI::RPCMode rpc_mode; - - GDScript *_script; + mutable Variant *_constants_ptr = nullptr; + int _constant_count = 0; + const StringName *_global_names_ptr = nullptr; + int _global_names_count = 0; + const int *_default_arg_ptr = nullptr; + int _default_arg_count = 0; + int _operator_funcs_count = 0; + const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr; + int _setters_count = 0; + const Variant::ValidatedSetter *_setters_ptr = nullptr; + int _getters_count = 0; + const Variant::ValidatedGetter *_getters_ptr = nullptr; + int _keyed_setters_count = 0; + const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr; + int _keyed_getters_count = 0; + const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr; + int _indexed_setters_count = 0; + const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr; + int _indexed_getters_count = 0; + const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr; + int _builtin_methods_count = 0; + const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr; + int _constructors_count = 0; + const Variant::ValidatedConstructor *_constructors_ptr = nullptr; + int _utilities_count = 0; + const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr; + int _gds_utilities_count = 0; + const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; + int _methods_count = 0; + MethodBind **_methods_ptr = nullptr; + const int *_code_ptr = nullptr; + int _code_size = 0; + int _argument_count = 0; + int _stack_size = 0; + int _instruction_args_size = 0; + int _ptrcall_args_size = 0; + + int _initial_line = 0; + bool _static = false; + MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + + GDScript *_script = nullptr; StringName name; Vector<Variant> constants; Vector<StringName> global_names; -#ifdef TOOLS_ENABLED - Vector<StringName> named_globals; -#endif Vector<int> default_arguments; + Vector<Variant::ValidatedOperatorEvaluator> operator_funcs; + Vector<Variant::ValidatedSetter> setters; + Vector<Variant::ValidatedGetter> getters; + Vector<Variant::ValidatedKeyedSetter> keyed_setters; + Vector<Variant::ValidatedKeyedGetter> keyed_getters; + Vector<Variant::ValidatedIndexedSetter> indexed_setters; + Vector<Variant::ValidatedIndexedGetter> indexed_getters; + Vector<Variant::ValidatedBuiltInMethod> builtin_methods; + Vector<Variant::ValidatedConstructor> constructors; + Vector<Variant::ValidatedUtilityFunction> utilities; + Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities; + Vector<MethodBind *> methods; Vector<int> code; Vector<GDScriptDataType> argument_types; GDScriptDataType return_type; #ifdef TOOLS_ENABLED Vector<StringName> arg_names; + Vector<Variant> default_arg_values; #endif List<StackDebug> stack_debug; @@ -270,34 +398,34 @@ private: friend class GDScriptLanguage; - SelfList<GDScriptFunction> function_list; + SelfList<GDScriptFunction> function_list{ this }; #ifdef DEBUG_ENABLED CharString func_cname; - const char *_func_cname; + const char *_func_cname = nullptr; struct Profile { StringName signature; - uint64_t call_count; - uint64_t self_time; - uint64_t total_time; - uint64_t frame_call_count; - uint64_t frame_self_time; - uint64_t frame_total_time; - uint64_t last_frame_call_count; - uint64_t last_frame_self_time; - uint64_t last_frame_total_time; + uint64_t call_count = 0; + uint64_t self_time = 0; + uint64_t total_time = 0; + uint64_t frame_call_count = 0; + uint64_t frame_self_time = 0; + uint64_t frame_total_time = 0; + uint64_t last_frame_call_count = 0; + uint64_t last_frame_self_time = 0; + uint64_t last_frame_total_time = 0; } profile; #endif public: struct CallState { - - ObjectID script_id; + GDScript *script; + GDScriptInstance *instance; #ifdef DEBUG_ENABLED + StringName function_name; String script_path; #endif - ObjectID instance_id; Vector<uint8_t> stack; int stack_size; Variant self; @@ -340,16 +468,24 @@ public: ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), Variant()); return default_arguments[p_idx]; } +#ifdef TOOLS_ENABLED + const Vector<Variant> &get_default_arg_values() const { + return default_arg_values; + } +#endif // TOOLS_ENABLED Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr); +#ifdef DEBUG_ENABLED + void disassemble(const Vector<String> &p_code_lines) const; +#endif + _FORCE_INLINE_ MultiplayerAPI::RPCMode get_rpc_mode() const { return rpc_mode; } GDScriptFunction(); ~GDScriptFunction(); }; class GDScriptFunctionState : public Reference { - GDCLASS(GDScriptFunctionState, Reference); friend class GDScriptFunction; GDScriptFunction *function; @@ -357,12 +493,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 deleted file mode 100644 index 0199af642f..0000000000 --- a/modules/gdscript/gdscript_functions.cpp +++ /dev/null @@ -1,2065 +0,0 @@ -/*************************************************************************/ -/* gdscript_functions.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_functions.h" - -#include "core/class_db.h" -#include "core/func_ref.h" -#include "core/io/json.h" -#include "core/io/marshalls.h" -#include "core/math/math_funcs.h" -#include "core/os/os.h" -#include "core/reference.h" -#include "core/variant_parser.h" -#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] = { - "sin", - "cos", - "tan", - "sinh", - "cosh", - "tanh", - "asin", - "acos", - "atan", - "atan2", - "sqrt", - "fmod", - "fposmod", - "posmod", - "floor", - "ceil", - "round", - "abs", - "sign", - "pow", - "log", - "exp", - "is_nan", - "is_inf", - "is_equal_approx", - "is_zero_approx", - "ease", - "step_decimals", - "stepify", - "lerp", - "lerp_angle", - "inverse_lerp", - "range_lerp", - "smoothstep", - "move_toward", - "dectime", - "randomize", - "randi", - "randf", - "rand_range", - "seed", - "rand_seed", - "deg2rad", - "rad2deg", - "linear2db", - "db2linear", - "polar2cartesian", - "cartesian2polar", - "wrapi", - "wrapf", - "max", - "min", - "clamp", - "nearest_po2", - "weakref", - "funcref", - "convert", - "typeof", - "type_exists", - "char", - "ord", - "str", - "print", - "printt", - "prints", - "printerr", - "printraw", - "print_debug", - "push_error", - "push_warning", - "var2str", - "str2var", - "var2bytes", - "bytes2var", - "range", - "load", - "inst2dict", - "dict2inst", - "validate_json", - "parse_json", - "to_json", - "hash", - "Color8", - "ColorN", - "print_stack", - "get_stack", - "instance_from_id", - "len", - "is_instance_valid", - }; - - return _names[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 - -#define VALIDATE_ARG_COUNT(m_count) \ - if (p_arg_count < m_count) { \ - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ - r_error.argument = 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_ret = Variant(); \ - return; \ - } - -#define VALIDATE_ARG_NUM(m_arg) \ - if (!p_args[m_arg]->is_num()) { \ - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ - r_error.argument = m_arg; \ - r_error.expected = Variant::FLOAT; \ - r_ret = Variant(); \ - return; \ - } - -#else - -#define VALIDATE_ARG_COUNT(m_count) -#define VALIDATE_ARG_NUM(m_arg) -#endif - - //using a switch, so the compiler generates a jumptable - - switch (p_func) { - - case MATH_SIN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::sin((double)*p_args[0]); - } break; - case MATH_COS: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::cos((double)*p_args[0]); - } break; - case MATH_TAN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::tan((double)*p_args[0]); - } break; - case MATH_SINH: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::sinh((double)*p_args[0]); - } break; - case MATH_COSH: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::cosh((double)*p_args[0]); - } break; - case MATH_TANH: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::tanh((double)*p_args[0]); - } break; - case MATH_ASIN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::asin((double)*p_args[0]); - } break; - case MATH_ACOS: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::acos((double)*p_args[0]); - } break; - case MATH_ATAN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::atan((double)*p_args[0]); - } break; - case MATH_ATAN2: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::atan2((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_SQRT: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::sqrt((double)*p_args[0]); - } break; - case MATH_FMOD: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::fmod((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_FPOSMOD: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::fposmod((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_POSMOD: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::posmod((int)*p_args[0], (int)*p_args[1]); - } break; - case MATH_FLOOR: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::floor((double)*p_args[0]); - } break; - case MATH_CEIL: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::ceil((double)*p_args[0]); - } break; - case MATH_ROUND: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::round((double)*p_args[0]); - } break; - 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; - r_ret = Variant(); - } - } break; - 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; - r_ret = Variant(); - } - } break; - case MATH_POW: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::pow((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_LOG: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::log((double)*p_args[0]); - } break; - case MATH_EXP: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::exp((double)*p_args[0]); - } break; - case MATH_ISNAN: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::is_nan((double)*p_args[0]); - } break; - case MATH_ISINF: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::is_inf((double)*p_args[0]); - } break; - case MATH_ISEQUALAPPROX: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::is_equal_approx((real_t)*p_args[0], (real_t)*p_args[1]); - } break; - case MATH_ISZEROAPPROX: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::is_zero_approx((real_t)*p_args[0]); - } break; - case MATH_EASE: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::ease((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_STEP_DECIMALS: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::step_decimals((double)*p_args[0]); - } break; - case MATH_STEPIFY: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::stepify((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_LERP: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(2); - 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]).lerp((Vector2)*p_args[1], t); - } break; - case Variant::VECTOR3: { - r_ret = (p_args[0]->operator Vector3()).lerp(p_args[1]->operator Vector3(), t); - } break; - case Variant::COLOR: { - r_ret = ((Color)*p_args[0]).lerp((Color)*p_args[1], t); - } break; - default: { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::lerp((double)*p_args[0], (double)*p_args[1], t); - } break; - } - } break; - case MATH_LERP_ANGLE: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::lerp_angle((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_INVERSE_LERP: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::inverse_lerp((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_RANGE_LERP: { - VALIDATE_ARG_COUNT(5); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - VALIDATE_ARG_NUM(3); - VALIDATE_ARG_NUM(4); - r_ret = Math::range_lerp((double)*p_args[0], (double)*p_args[1], (double)*p_args[2], (double)*p_args[3], (double)*p_args[4]); - } break; - case MATH_SMOOTHSTEP: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::smoothstep((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_MOVE_TOWARD: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::move_toward((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_DECTIME: { - VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - r_ret = Math::dectime((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - case MATH_RANDOMIZE: { - VALIDATE_ARG_COUNT(0); - Math::randomize(); - r_ret = Variant(); - } break; - case MATH_RAND: { - VALIDATE_ARG_COUNT(0); - r_ret = Math::rand(); - } break; - case MATH_RANDF: { - VALIDATE_ARG_COUNT(0); - r_ret = Math::randf(); - } break; - case MATH_RANDOM: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - r_ret = Math::random((double)*p_args[0], (double)*p_args[1]); - } break; - case MATH_SEED: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - uint64_t seed = *p_args[0]; - Math::seed(seed); - r_ret = Variant(); - } break; - case MATH_RANDSEED: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - uint64_t seed = *p_args[0]; - int ret = Math::rand_from_seed(&seed); - Array reta; - reta.push_back(ret); - reta.push_back(seed); - r_ret = reta; - - } break; - case MATH_DEG2RAD: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::deg2rad((double)*p_args[0]); - } break; - case MATH_RAD2DEG: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::rad2deg((double)*p_args[0]); - } break; - case MATH_LINEAR2DB: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::linear2db((double)*p_args[0]); - } break; - case MATH_DB2LINEAR: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - r_ret = Math::db2linear((double)*p_args[0]); - } break; - case MATH_POLAR2CARTESIAN: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - double r = *p_args[0]; - double th = *p_args[1]; - r_ret = Vector2(r * Math::cos(th), r * Math::sin(th)); - } break; - case MATH_CARTESIAN2POLAR: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - double x = *p_args[0]; - double y = *p_args[1]; - r_ret = Vector2(Math::sqrt(x * x + y * y), Math::atan2(y, x)); - } break; - case MATH_WRAP: { - VALIDATE_ARG_COUNT(3); - r_ret = Math::wrapi((int64_t)*p_args[0], (int64_t)*p_args[1], (int64_t)*p_args[2]); - } break; - case MATH_WRAPF: { - VALIDATE_ARG_COUNT(3); - r_ret = Math::wrapf((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); - } break; - 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); - } else { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - - real_t a = *p_args[0]; - real_t b = *p_args[1]; - - r_ret = MAX(a, b); - } - - } break; - 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); - } else { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - - real_t a = *p_args[0]; - real_t b = *p_args[1]; - - r_ret = MIN(a, b); - } - } break; - 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]; - r_ret = CLAMP(a, b, c); - } else { - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - - real_t a = *p_args[0]; - real_t b = *p_args[1]; - real_t c = *p_args[2]; - - r_ret = CLAMP(a, b, c); - } - } break; - case LOGIC_NEAREST_PO2: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - int64_t num = *p_args[0]; - r_ret = next_power_of_2(num); - } break; - case OBJ_WEAKREF: { - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() == Variant::OBJECT) { - if (p_args[0]->is_ref()) { - Ref<WeakRef> wref = memnew(WeakRef); - REF r = *p_args[0]; - if (r.is_valid()) { - wref->set_ref(r); - } - r_ret = wref; - } else { - Ref<WeakRef> wref = memnew(WeakRef); - Object *obj = *p_args[0]; - if (obj) { - wref->set_obj(obj); - } - r_ret = wref; - } - } else if (p_args[0]->get_type() == Variant::NIL) { - Ref<WeakRef> wref = memnew(WeakRef); - r_ret = wref; - } else { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = Variant(); - return; - } - } break; - case FUNC_FUNCREF: { - VALIDATE_ARG_COUNT(2); - if (p_args[0]->get_type() != Variant::OBJECT) { - - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = Variant(); - return; - } - if (p_args[1]->get_type() != Variant::STRING && p_args[1]->get_type() != Variant::NODE_PATH) { - - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = Variant::STRING; - r_ret = Variant(); - return; - } - - Ref<FuncRef> fr = memnew(FuncRef); - - fr->set_instance(*p_args[0]); - fr->set_function(*p_args[1]); - - r_ret = fr; - - } break; - case TYPE_CONVERT: { - VALIDATE_ARG_COUNT(2); - VALIDATE_ARG_NUM(1); - 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; - r_error.expected = Variant::INT; - 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]); - - } break; - case TEXT_CHAR: { - VALIDATE_ARG_COUNT(1); - VALIDATE_ARG_NUM(0); - CharType result[2] = { *p_args[0], 0 }; - 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; - r_ret = Variant(); - return; - } - - 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; - r_ret = RTR("Expected a string of length 1 (a character)."); - return; - } - - r_ret = str.get(0); - - } break; - case TEXT_STR: { - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - - return; - } - String str; - for (int i = 0; i < p_arg_count; i++) { - - String os = p_args[i]->operator String(); - - if (i == 0) - str = os; - 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(); - } - - print_line(str); - r_ret = Variant(); - - } break; - case TEXT_PRINT_TABBED: { - - String str; - for (int i = 0; i < p_arg_count; i++) { - - if (i) - str += "\t"; - str += p_args[i]->operator String(); - } - - print_line(str); - r_ret = Variant(); - - } break; - case TEXT_PRINT_SPACED: { - - String str; - for (int i = 0; i < p_arg_count; i++) { - - if (i) - str += " "; - str += p_args[i]->operator String(); - } - - print_line(str); - r_ret = Variant(); - - } break; - - case TEXT_PRINTERR: { - - String str; - for (int i = 0; i < p_arg_count; i++) { - - str += p_args[i]->operator String(); - } - - print_error(str); - r_ret = Variant(); - - } break; - case TEXT_PRINTRAW: { - String str; - for (int i = 0; i < p_arg_count; i++) { - - str += p_args[i]->operator String(); - } - - OS::get_singleton()->print("%s", str.utf8().get_data()); - r_ret = Variant(); - - } break; - case TEXT_PRINT_DEBUG: { - String str; - for (int i = 0; i < p_arg_count; i++) { - - str += p_args[i]->operator String(); - } - - ScriptLanguage *script = GDScriptLanguage::get_singleton(); - if (script->debug_get_stack_level_count() > 0) { - str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; - } - - print_line(str); - r_ret = Variant(); - } break; - case PUSH_ERROR: { - 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; - r_ret = Variant(); - break; - } - - String message = *p_args[0]; - ERR_PRINT(message); - r_ret = Variant(); - } break; - case PUSH_WARNING: { - 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; - r_ret = Variant(); - break; - } - - String message = *p_args[0]; - WARN_PRINT(message); - r_ret = Variant(); - } break; - case VAR_TO_STR: { - VALIDATE_ARG_COUNT(1); - String vars; - VariantWriter::write_to_string(*p_args[0], vars); - r_ret = vars; - } break; - case STR_TO_VAR: { - 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; - r_ret = Variant(); - return; - } - r_ret = *p_args[0]; - - VariantParser::StreamString ss; - ss.s = *p_args[0]; - - String errs; - int line; - (void)VariantParser::parse(&ss, r_ret, errs, line); - } break; - case VAR_TO_BYTES: { - bool full_objects = false; - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - return; - } else if (p_arg_count > 2) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 2; - r_ret = Variant(); - } else if (p_arg_count == 2) { - if (p_args[1]->get_type() != Variant::BOOL) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = Variant::BOOL; - r_ret = Variant(); - return; - } - full_objects = *p_args[1]; - } - - PackedByteArray barr; - int len; - Error err = encode_variant(*p_args[0], nullptr, len, full_objects); - if (err) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::NIL; - r_ret = "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."; - return; - } - - barr.resize(len); - { - uint8_t *w = barr.ptrw(); - encode_variant(*p_args[0], w, len, full_objects); - } - r_ret = barr; - } break; - case BYTES_TO_VAR: { - bool allow_objects = false; - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - return; - } else if (p_arg_count > 2) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 2; - r_ret = Variant(); - } else if (p_arg_count == 2) { - if (p_args[1]->get_type() != Variant::BOOL) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = Variant::BOOL; - r_ret = Variant(); - return; - } - allow_objects = *p_args[1]; - } - - if (p_args[0]->get_type() != Variant::PACKED_BYTE_ARRAY) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = Variant::PACKED_BYTE_ARRAY; - r_ret = Variant(); - return; - } - - PackedByteArray varr = *p_args[0]; - Variant ret; - { - const uint8_t *r = varr.ptr(); - Error err = decode_variant(ret, r, varr.size(), nullptr, allow_objects); - if (err != OK) { - r_ret = RTR("Not enough bytes for decoding bytes, or invalid format."); - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::PACKED_BYTE_ARRAY; - return; - } - } - - r_ret = ret; - - } 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_ret = Variant(); - - } break; - case 1: { - - VALIDATE_ARG_NUM(0); - int count = *p_args[0]; - Array arr; - if (count <= 0) { - r_ret = arr; - return; - } - Error err = arr.resize(count); - if (err != OK) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - r_ret = Variant(); - return; - } - - for (int i = 0; i < count; i++) { - arr[i] = i; - } - - r_ret = arr; - } break; - case 2: { - - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - - int from = *p_args[0]; - int to = *p_args[1]; - - Array arr; - if (from >= to) { - r_ret = arr; - return; - } - Error err = arr.resize(to - from); - if (err != OK) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - r_ret = Variant(); - return; - } - 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); - - int from = *p_args[0]; - 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; - } - - Array arr; - if (from >= to && incr > 0) { - r_ret = arr; - return; - } - if (from <= to && incr < 0) { - r_ret = arr; - return; - } - - //calculate how many - int count = 0; - if (incr > 0) { - - count = ((to - from - 1) / incr) + 1; - } else { - - count = ((from - to - 1) / -incr) + 1; - } - - Error err = arr.resize(count); - - if (err != OK) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - r_ret = Variant(); - return; - } - - if (incr > 0) { - int idx = 0; - for (int i = from; i < to; i += incr) { - arr[idx++] = i; - } - } else { - - int idx = 0; - for (int i = from; i > to; i += incr) { - arr[idx++] = i; - } - } - - r_ret = arr; - } break; - default: { - - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 3; - r_ret = Variant(); - - } break; - } - - } break; - case RESOURCE_LOAD: { - 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; - r_ret = Variant(); - } else { - r_ret = ResourceLoader::load(*p_args[0]); - } - - } break; - case INST2DICT: { - - VALIDATE_ARG_COUNT(1); - - if (p_args[0]->get_type() == Variant::NIL) { - r_ret = Variant(); - } else if (p_args[0]->get_type() != Variant::OBJECT) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - 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; - r_ret = RTR("Not based on a script"); - return; - } - - GDScript *p = base.ptr(); - Vector<StringName> sname; - - while (p->_owner) { - - sname.push_back(p->name); - p = p->_owner; - } - sname.invert(); - - if (!p->path.is_resource_file()) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::DICTIONARY; - r_ret = Variant(); - - r_ret = RTR("Not based on a resource file"); - - return; - } - - NodePath cp(sname, Vector<StringName>(), false); - - Dictionary d; - d["@subpath"] = cp; - d["@path"] = p->get_path(); - - for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) { - if (!d.has(E->key())) { - d[E->key()] = ins->members[E->get().index]; - } - } - r_ret = d; - } - } - - } 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; - r_ret = Variant(); - - return; - } - - 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; - r_ret = RTR("Invalid instance dictionary format (missing @path)"); - - return; - } - - 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; - r_ret = RTR("Invalid instance dictionary format (can't load script at @path)"); - return; - } - - 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; - r_ret = Variant(); - r_ret = RTR("Invalid instance dictionary format (invalid script at @path)"); - return; - } - - NodePath sub; - if (d.has("@subpath")) { - sub = d["@subpath"]; - } - - 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; - r_ret = Variant(); - r_ret = RTR("Invalid instance dictionary (invalid subclasses)"); - return; - } - } - r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error); - - GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(r_ret)->get_script_instance()); - Ref<GDScript> gd_ref = ins->get_script(); - - for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { - if (d.has(E->key())) { - ins->members.write[E->get().index] = d[E->key()]; - } - } - - } break; - case VALIDATE_JSON: { - - 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; - r_ret = Variant(); - return; - } - - String errs; - int errl; - - Error err = JSON::parse(*p_args[0], r_ret, errs, errl); - - if (err != OK) { - r_ret = itos(errl) + ":" + errs; - } else { - r_ret = ""; - } - - } break; - case PARSE_JSON: { - - 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; - r_ret = Variant(); - return; - } - - String errs; - int errl; - - Error err = JSON::parse(*p_args[0], r_ret, errs, errl); - - if (err != OK) { - r_ret = Variant(); - ERR_PRINT(vformat("Error parsing JSON at line %s: %s", errl, errs)); - } - - } break; - case TO_JSON: { - VALIDATE_ARG_COUNT(1); - - 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; - r_ret = Variant(); - - return; - } - if (p_arg_count > 4) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 4; - r_ret = Variant(); - - return; - } - - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); - VALIDATE_ARG_NUM(2); - - Color color((float)*p_args[0] / 255.0f, (float)*p_args[1] / 255.0f, (float)*p_args[2] / 255.0f); - - if (p_arg_count == 4) { - VALIDATE_ARG_NUM(3); - color.a = (float)*p_args[3] / 255.0f; - } - - r_ret = color; - - } break; - case COLORN: { - - if (p_arg_count < 1) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; - r_ret = Variant(); - return; - } - - if (p_arg_count > 2) { - r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 2; - r_ret = Variant(); - return; - } - - if (p_args[0]->get_type() != Variant::STRING) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_ret = Variant(); - } else { - Color color = Color::named(*p_args[0]); - if (p_arg_count == 2) { - VALIDATE_ARG_NUM(1); - color.a = *p_args[1]; - } - r_ret = color; - } - - } break; - - case PRINT_STACK: { - VALIDATE_ARG_COUNT(0); - - 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; - - case GET_STACK: { - VALIDATE_ARG_COUNT(0); - - 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); - frame["line"] = script->debug_get_stack_level_line(i); - ret.push_back(frame); - }; - r_ret = ret; - } 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; - r_error.argument = 0; - r_error.expected = Variant::INT; - r_ret = Variant(); - break; - } - - ObjectID id = *p_args[0]; - r_ret = ObjectDB::get_instance(id); - - } 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; - default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 0; - r_error.expected = Variant::OBJECT; - r_ret = Variant(); - r_ret = RTR("Object can't provide a length."); - } - } - - } break; - case IS_INSTANCE_VALID: { - - VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::OBJECT) { - r_ret = false; - } else { - Object *obj = p_args[0]->get_validated_object(); - r_ret = obj != nullptr; - } - - } 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: - case MATH_SINH: - case MATH_COSH: - case MATH_TANH: - case MATH_ASIN: - case MATH_ACOS: - case MATH_ATAN: - case MATH_ATAN2: - case MATH_SQRT: - case MATH_FMOD: - case MATH_FPOSMOD: - case MATH_POSMOD: - case MATH_FLOOR: - case MATH_CEIL: - case MATH_ROUND: - case MATH_ABS: - case MATH_SIGN: - case MATH_POW: - case MATH_LOG: - case MATH_EXP: - case MATH_ISNAN: - case MATH_ISINF: - case MATH_EASE: - case MATH_STEP_DECIMALS: - case MATH_STEPIFY: - case MATH_LERP: - case MATH_INVERSE_LERP: - case MATH_RANGE_LERP: - case MATH_SMOOTHSTEP: - case MATH_MOVE_TOWARD: - case MATH_DECTIME: - case MATH_DEG2RAD: - case MATH_RAD2DEG: - case MATH_LINEAR2DB: - case MATH_DB2LINEAR: - case MATH_POLAR2CARTESIAN: - case MATH_CARTESIAN2POLAR: - case MATH_WRAP: - case MATH_WRAPF: - case LOGIC_MAX: - case LOGIC_MIN: - case LOGIC_CLAMP: - case LOGIC_NEAREST_PO2: - case TYPE_CONVERT: - case TYPE_OF: - case TYPE_EXISTS: - case TEXT_CHAR: - case TEXT_ORD: - case TEXT_STR: - case COLOR8: - case LEN: - // enable for debug only, otherwise not desirable - case GEN_RANGE: - return true; - default: - return false; - } - - return false; -} - -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; - return mi; - - } break; - case MATH_COS: { - MethodInfo mi("cos", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_TAN: { - MethodInfo mi("tan", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SINH: { - MethodInfo mi("sinh", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_COSH: { - MethodInfo mi("cosh", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_TANH: { - MethodInfo mi("tanh", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ASIN: { - MethodInfo mi("asin", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ACOS: { - MethodInfo mi("acos", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ATAN: { - MethodInfo mi("atan", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ATAN2: { - MethodInfo mi("atan2", PropertyInfo(Variant::FLOAT, "y"), PropertyInfo(Variant::FLOAT, "x")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SQRT: { - MethodInfo mi("sqrt", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_FMOD: { - MethodInfo mi("fmod", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_FPOSMOD: { - MethodInfo mi("fposmod", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_POSMOD: { - MethodInfo mi("posmod", PropertyInfo(Variant::INT, "a"), PropertyInfo(Variant::INT, "b")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_FLOOR: { - MethodInfo mi("floor", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_CEIL: { - MethodInfo mi("ceil", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ROUND: { - MethodInfo mi("round", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ABS: { - MethodInfo mi("abs", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SIGN: { - MethodInfo mi("sign", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_POW: { - MethodInfo mi("pow", PropertyInfo(Variant::FLOAT, "base"), PropertyInfo(Variant::FLOAT, "exp")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_LOG: { - MethodInfo mi("log", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_EXP: { - MethodInfo mi("exp", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_ISNAN: { - MethodInfo mi("is_nan", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_ISINF: { - MethodInfo mi("is_inf", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_ISEQUALAPPROX: { - MethodInfo mi("is_equal_approx", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_ISZEROAPPROX: { - MethodInfo mi("is_zero_approx", PropertyInfo(Variant::FLOAT, "s")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - case MATH_EASE: { - MethodInfo mi("ease", PropertyInfo(Variant::FLOAT, "s"), PropertyInfo(Variant::FLOAT, "curve")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_STEP_DECIMALS: { - MethodInfo mi("step_decimals", PropertyInfo(Variant::FLOAT, "step")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_STEPIFY: { - MethodInfo mi("stepify", PropertyInfo(Variant::FLOAT, "s"), PropertyInfo(Variant::FLOAT, "step")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_LERP: { - MethodInfo mi("lerp", PropertyInfo(Variant::NIL, "from"), PropertyInfo(Variant::NIL, "to"), PropertyInfo(Variant::FLOAT, "weight")); - mi.return_val.type = Variant::NIL; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; - } break; - case MATH_LERP_ANGLE: { - MethodInfo mi("lerp_angle", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "weight")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_INVERSE_LERP: { - MethodInfo mi("inverse_lerp", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "weight")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RANGE_LERP: { - MethodInfo mi("range_lerp", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "istart"), PropertyInfo(Variant::FLOAT, "istop"), PropertyInfo(Variant::FLOAT, "ostart"), PropertyInfo(Variant::FLOAT, "ostop")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SMOOTHSTEP: { - MethodInfo mi("smoothstep", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "weight")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_MOVE_TOWARD: { - MethodInfo mi("move_toward", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "delta")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_DECTIME: { - MethodInfo mi("dectime", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "amount"), PropertyInfo(Variant::FLOAT, "step")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RANDOMIZE: { - MethodInfo mi("randomize"); - mi.return_val.type = Variant::NIL; - return mi; - } break; - case MATH_RAND: { - MethodInfo mi("randi"); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_RANDF: { - MethodInfo mi("randf"); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RANDOM: { - MethodInfo mi("rand_range", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_SEED: { - MethodInfo mi("seed", PropertyInfo(Variant::INT, "seed")); - mi.return_val.type = Variant::NIL; - return mi; - } break; - case MATH_RANDSEED: { - MethodInfo mi("rand_seed", PropertyInfo(Variant::INT, "seed")); - mi.return_val.type = Variant::ARRAY; - return mi; - } break; - case MATH_DEG2RAD: { - MethodInfo mi("deg2rad", PropertyInfo(Variant::FLOAT, "deg")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_RAD2DEG: { - MethodInfo mi("rad2deg", PropertyInfo(Variant::FLOAT, "rad")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_LINEAR2DB: { - MethodInfo mi("linear2db", PropertyInfo(Variant::FLOAT, "nrg")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_DB2LINEAR: { - MethodInfo mi("db2linear", PropertyInfo(Variant::FLOAT, "db")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case MATH_POLAR2CARTESIAN: { - MethodInfo mi("polar2cartesian", PropertyInfo(Variant::FLOAT, "r"), PropertyInfo(Variant::FLOAT, "th")); - mi.return_val.type = Variant::VECTOR2; - return mi; - } break; - case MATH_CARTESIAN2POLAR: { - MethodInfo mi("cartesian2polar", PropertyInfo(Variant::FLOAT, "x"), PropertyInfo(Variant::FLOAT, "y")); - mi.return_val.type = Variant::VECTOR2; - return mi; - } break; - case MATH_WRAP: { - MethodInfo mi("wrapi", PropertyInfo(Variant::INT, "value"), PropertyInfo(Variant::INT, "min"), PropertyInfo(Variant::INT, "max")); - mi.return_val.type = Variant::INT; - return mi; - } break; - case MATH_WRAPF: { - MethodInfo mi("wrapf", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case LOGIC_MAX: { - MethodInfo mi("max", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - - } break; - case LOGIC_MIN: { - MethodInfo mi("min", PropertyInfo(Variant::FLOAT, "a"), PropertyInfo(Variant::FLOAT, "b")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case LOGIC_CLAMP: { - MethodInfo mi("clamp", PropertyInfo(Variant::FLOAT, "value"), PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max")); - mi.return_val.type = Variant::FLOAT; - return mi; - } break; - case LOGIC_NEAREST_PO2: { - MethodInfo mi("nearest_po2", PropertyInfo(Variant::INT, "value")); - mi.return_val.type = Variant::INT; - 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"; - - return mi; - - } break; - case FUNC_FUNCREF: { - - MethodInfo mi("funcref", PropertyInfo(Variant::OBJECT, "instance"), PropertyInfo(Variant::STRING, "funcname")); - mi.return_val.type = Variant::OBJECT; - mi.return_val.class_name = "FuncRef"; - return mi; - - } break; - case TYPE_CONVERT: { - - MethodInfo mi("convert", PropertyInfo(Variant::NIL, "what", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::INT, "type")); - mi.return_val.type = Variant::NIL; - 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; - return mi; - - } break; - case TEXT_PRINT: { - - MethodInfo mi("print"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINT_TABBED: { - - MethodInfo mi("printt"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINT_SPACED: { - - MethodInfo mi("prints"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINTERR: { - - MethodInfo mi("printerr"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINTRAW: { - - MethodInfo mi("printraw"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } break; - case TEXT_PRINT_DEBUG: { - - MethodInfo mi("print_debug"); - mi.return_val.type = Variant::NIL; - mi.flags |= METHOD_FLAG_VARARG; - return mi; - - } 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; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - 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; - return mi; - } break; - - case PRINT_STACK: { - MethodInfo mi("print_stack"); - mi.return_val.type = Variant::NIL; - return mi; - } break; - case GET_STACK: { - MethodInfo mi("get_stack"); - mi.return_val.type = Variant::ARRAY; - return mi; - } break; - - case INSTANCE_FROM_ID: { - MethodInfo mi("instance_from_id", PropertyInfo(Variant::INT, "instance_id")); - mi.return_val.type = Variant::OBJECT; - return mi; - } break; - case LEN: { - MethodInfo mi("len", 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 IS_INSTANCE_VALID: { - MethodInfo mi("is_instance_valid", PropertyInfo(Variant::OBJECT, "instance")); - mi.return_val.type = Variant::BOOL; - return mi; - } break; - default: { - - ERR_FAIL_V(MethodInfo()); - } break; - } -#endif - MethodInfo mi; - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - return mi; -} diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 17077567c7..a77fb14064 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -30,8775 +30,4103 @@ #include "gdscript_parser.h" -#include "core/core_string_names.h" -#include "core/engine.h" +#include "core/config/project_settings.h" #include "core/io/resource_loader.h" +#include "core/math/math_defs.h" #include "core/os/file_access.h" -#include "core/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() { +#ifdef DEBUG_ENABLED +#include "core/os/os.h" +#include "core/string/string_builder.h" +#endif // DEBUG_ENABLED - T *t = memnew(T); +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +static HashMap<StringName, Variant::Type> builtin_types; +Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { + if (builtin_types.is_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."); + } + } - t->next = list; - list = t; + if (builtin_types.has(p_type)) { + return builtin_types[p_type]; + } + return Variant::VARIANT_MAX; +} - if (!head) - head = t; +void GDScriptParser::cleanup() { + builtin_types.clear(); +} - t->line = tokenizer->get_token_line(); - t->column = tokenizer->get_token_column(); - return t; +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); + } } -#ifdef DEBUG_ENABLED -static String _find_function_name(const GDScriptParser::OperatorNode *p_call); -#endif // DEBUG_ENABLED +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::_end_statement() { +GDScriptParser::~GDScriptParser() { + clear(); +} - if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) { - tokenizer->advance(); - return true; //handle next - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - return true; //will be handled properly +void GDScriptParser::clear() { + while (list != nullptr) { + Node *element = list; + list = list->next; + memdelete(element); } - return false; + head = nullptr; + list = nullptr; + _is_tool = false; + for_completion = false; + errors.clear(); + multiline_stack.clear(); } -bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { - - if (tokenizer->get_token() != GDScriptTokenizer::TK_COLON) { - // report location at the previous token (on the previous line) - int error_line = tokenizer->get_token_line(-1); - int error_column = tokenizer->get_token_column(-1); - _set_error("':' expected at end of line.", error_line, error_column); - return false; +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 }); } - tokenizer->advance(); +} - if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) { - 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) { + ERR_FAIL_COND(p_source == nullptr); + Vector<String> symbols; + if (!p_symbol1.is_empty()) { + symbols.push_back(p_symbol1); } - - 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; + if (!p_symbol2.is_empty()) { + symbols.push_back(p_symbol2); } + if (!p_symbol3.is_empty()) { + symbols.push_back(p_symbol3); + } + if (!p_symbol4.is_empty()) { + symbols.push_back(p_symbol4); + } + push_warning(p_source, p_code, symbols); +} - while (true) { - if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { - - return false; //wtf - } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_EOF) { - return false; - } else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { - - int indent = tokenizer->get_token_line_indent(); - int tabs = tokenizer->get_token_line_tab_indent(); - IndentLevel current_level = indent_level.back()->get(); - IndentLevel new_indent(indent, tabs); - if (new_indent.is_mixed(current_level)) { - _set_error("Mixed tabs and spaces in indentation."); - return false; - } - - if (indent <= current_level.indent) { - return false; - } +void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { + ERR_FAIL_COND(p_source == nullptr); + if (is_ignoring_warnings) { + return; + } + 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.is_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.is_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] == char32_t(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.is_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.is_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; - } - - 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; - } - - break; - } - } - } + advance(); + } +} - if (path == "") { - _set_error("Path expected after $."); - return nullptr; - } +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. + } + } +} - 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::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); +} - //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) { +bool GDScriptParser::is_statement_end() { + return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF); +} - //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) { +void GDScriptParser::end_statement(const String &p_context) { + bool found = false; + while (is_statement_end() && !is_at_end()) { + // Remove sequential newlines/semicolons. + found = true; + advance(); + } + if (!found && !is_at_end()) { + 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_NAN; - constant->datatype = _type_from_variant(constant->value); - tokenizer->advance(); - expr = constant; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) { - - //constant defined by tokenizer - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) { - _set_error("Expected '(' after 'preload'"); - return nullptr; - } - tokenizer->advance(); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - completion_cursor = StringName(); - completion_node = p_parent; - completion_type = COMPLETION_RESOURCE_PATH; - completion_class = current_class; - completion_function = current_function; - completion_line = tokenizer->get_token_line(); - completion_block = current_block; - completion_argument = 0; - completion_found = true; - tokenizer->advance(); +void GDScriptParser::parse_program() { + head = alloc_node<ClassNode>(); + current_class = head; + + if (match(GDScriptTokenizer::Token::ANNOTATION)) { + // Check for @tool annotation. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); + if (annotation != nullptr) { + if (annotation->name == "@tool") { + // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). + _is_tool = true; + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after "@tool" annotation.)"); + } + // @tool annotation has no specific target. + annotation->apply(this, nullptr); + } else { + annotation_stack.push_back(annotation); } + } + } - 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; - } - } - } - - 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.is_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); - } - } - if (!res.is_valid()) { - _set_error("Can't preload resource at path: " + path); - return nullptr; + parse_class_name(); } - } - - 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.is_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; + annotation->apply(this, head); + } else { + annotation_stack.push_back(annotation); } + } + } - if (identifier == StringName()) { - - _set_error("Built-in type constant or static function expected after \".\"."); - return nullptr; - } - if (!Variant::has_constant(bi_type, identifier)) { + parse_class_body(); - 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) { +#ifdef TOOLS_ENABLED + for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) { + if (E->get().new_line && E->get().comment.begins_with("##")) { + class_doc_line = MIN(class_doc_line, E->key()); + } + } + if (has_comment(class_doc_line)) { + get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false); + } +#endif // TOOLS_ENABLED - tokenizer->advance(); + if (!check(GDScriptTokenizer::Token::TK_EOF)) { + push_error("Expected end of file."); + } - OperatorNode *construct = alloc_node<OperatorNode>(); - construct->op = OperatorNode::OP_CALL; + clear_unused_annotations(); +} - TypeNode *tn = alloc_node<TypeNode>(); - tn->vtype = bi_type; - construct->arguments.push_back(tn); +GDScriptParser::ClassNode *GDScriptParser::parse_class() { + ClassNode *n_class = alloc_node<ClassNode>(); - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_CALL; - op->arguments.push_back(construct); + ClassNode *previous_class = current_class; + current_class = n_class; + n_class->outer = previous_class; - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - op->arguments.push_back(id); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) { + n_class->identifier = parse_identifier(); + } - if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) - return nullptr; + if (match(GDScriptTokenizer::Token::EXTENDS)) { + parse_extends(); + } - 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 { + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)"); + consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)"); - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = Variant::get_constant_value(bi_type, identifier); - cn->datatype = _type_from_variant(cn->value); - expr = cn; - } + if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { + current_class = previous_class; + return n_class; + } - } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) { - // We check with is_token_literal, as this allows us to use match/sync/etc. as a name - //function or constructor - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = OperatorNode::OP_CALL; - - //Do a quick Array and Dictionary Check. Replace if either require no arguments. - bool replaced = false; - - if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { - Variant::Type ct = tokenizer->get_token_type(); - if (!p_parsing_constant) { - if (ct == Variant::ARRAY) { - if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - ArrayNode *arr = alloc_node<ArrayNode>(); - expr = arr; - replaced = true; - tokenizer->advance(3); - } - } - if (ct == Variant::DICTIONARY) { - if (tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { - DictionaryNode *dict = alloc_node<DictionaryNode>(); - expr = dict; - replaced = true; - tokenizer->advance(3); - } - } - } + if (match(GDScriptTokenizer::Token::EXTENDS)) { + if (n_class->extends_used) { + push_error(R"(Cannot use "extends" more than once in the same class.)"); + } + parse_extends(); + end_statement("superclass"); + } - 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) { + parse_class_body(); - BuiltInFunctionNode *bn = alloc_node<BuiltInFunctionNode>(); - bn->function = tokenizer->get_token_built_in_func(); - op->arguments.push_back(bn); - tokenizer->advance(2); - } else { + consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); - SelfNode *self = alloc_node<SelfNode>(); - op->arguments.push_back(self); + current_class = previous_class; + return n_class; +} - StringName identifier; - if (_get_completable_identifier(COMPLETION_FUNCTION, identifier)) { - } +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(); + } - IdentifierNode *id = alloc_node<IdentifierNode>(); - id->name = identifier; - op->arguments.push_back(id); - tokenizer->advance(1); + // 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; + } + } - if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) { - _make_completable_call(0); - completion_node = op; + if (match(GDScriptTokenizer::Token::EXTENDS)) { + // Allow extends on the same line. + parse_extends(); + end_statement("superclass"); + } else { + end_statement("class_name statement"); + } +} - if (op->arguments[0]->type == GDScriptParser::Node::Type::TYPE_BUILT_IN_FUNCTION) { - BuiltInFunctionNode *bn = static_cast<BuiltInFunctionNode *>(op->arguments[0]); - if (bn->function == GDScriptFunctions::Function::RESOURCE_LOAD) { - completion_type = COMPLETION_RESOURCE_PATH; - } - } - } - if (!replaced) { - if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) - return nullptr; - expr = op; - } - } else if (tokenizer->is_token_literal(0, true)) { - // We check with is_token_literal, as this allows us to use match/sync/etc. as a name - //identifier (reference) - - const ClassNode *cln = current_class; - bool bfn = false; - StringName identifier; - int id_line = tokenizer->get_token_line(); - if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) { - } +void GDScriptParser::parse_extends() { + current_class->extends_used = true; - 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; + int chain_index = 0; -#ifdef DEBUG_ENABLED - LocalVarNode *lv = b->variables[identifier]; - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_OP_ASSIGN_ADD: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: - case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: - case GDScriptTokenizer::TK_OP_ASSIGN_DIV: - case GDScriptTokenizer::TK_OP_ASSIGN_MOD: - case GDScriptTokenizer::TK_OP_ASSIGN_MUL: - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: - case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: - case GDScriptTokenizer::TK_OP_ASSIGN_SUB: { - if (lv->assignments == 0) { - if (!lv->datatype.has_type) { - _set_error("Using assignment with operation on a variable that was never assigned."); - return nullptr; - } - _add_warning(GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, -1, identifier.operator String()); - } - [[fallthrough]]; - } - case GDScriptTokenizer::TK_OP_ASSIGN: { - lv->assignments += 1; - lv->usages--; // Assignment is not really usage - } break; - default: { - lv->usages++; - } - } -#endif // DEBUG_ENABLED - break; - } - b = b->parent_block; - } + if (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 (!bfn && p_parsing_constant) { - if (cln->constant_expressions.has(identifier)) { - expr = cln->constant_expressions[identifier].expression; - bfn = true; - } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { - //check from constants - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]]; - constant->datatype = _type_from_variant(constant->value); - constant->line = id_line; - expr = constant; - bfn = true; - } + if (!match(GDScriptTokenizer::Token::PERIOD)) { + return; + } + } - if (!bfn && GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { - //check from singletons - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = GDScriptLanguage::get_singleton()->get_named_globals_map()[identifier]; - constant->datatype = _type_from_variant(constant->value); - expr = constant; - bfn = true; - } + make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); - if (!dependencies_only) { - if (!bfn && ScriptServer::is_global_class(identifier)) { - Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (scr.is_valid() && scr->is_valid()) { - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = scr; - constant->datatype = _type_from_variant(constant->value); - expr = constant; - bfn = true; - } - } + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) { + return; + } + current_class->extends.push_back(previous.literal); - // Check parents for the constant - if (!bfn) { - // Using current_class instead of cln here, since cln is const* - _determine_inheritance(current_class, false); - if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) { - Map<StringName, Variant> parent_constants; - current_class->base_type.script_type->get_constants(&parent_constants); - if (parent_constants.has(identifier)) { - ConstantNode *constant = alloc_node<ConstantNode>(); - constant->value = parent_constants[identifier]; - constant->datatype = _type_from_variant(constant->value); - expr = constant; - bfn = true; - } - } - } - } - } + 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); + } +} - 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; - } +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; + } +#ifdef TOOLS_ENABLED + int doc_comment_line = member->start_line - 1; +#endif // TOOLS_ENABLED + + // Consume annotations. + while (!annotation_stack.is_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; + } +#ifdef TOOLS_ENABLED + if (last_annotation->start_line == doc_comment_line) { + doc_comment_line--; + } +#endif // TOOLS_ENABLED + } - } 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) { +#ifdef TOOLS_ENABLED + // Consume doc comments. + class_doc_line = MIN(class_doc_line, doc_comment_line - 1); + if (has_comment(doc_comment_line)) { + if constexpr (std::is_same_v<T, ClassNode>) { + get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true); + } else { + member->doc_description = get_doc_comment(doc_comment_line); + } + } +#endif // TOOLS_ENABLED - //single prefix operators like !expr +expr -expr ++expr --expr - alloc_node<OperatorNode>(); - Expression e; - e.is_op = true; + 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 (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).)"); } - 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 - } - - 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) { - - case OperatorNode::OP_IS: - case OperatorNode::OP_IS_BUILTIN: - priority = -1; - break; //before anything - - case OperatorNode::OP_BIT_INVERT: - priority = 0; - unary = true; - break; - case OperatorNode::OP_NEG: - case OperatorNode::OP_POS: - priority = 1; - unary = true; - break; - - case OperatorNode::OP_MUL: priority = 2; break; - case OperatorNode::OP_DIV: priority = 2; break; - case OperatorNode::OP_MOD: priority = 2; break; - - case OperatorNode::OP_ADD: priority = 3; break; - case OperatorNode::OP_SUB: priority = 3; break; - - case OperatorNode::OP_SHIFT_LEFT: priority = 4; break; - case OperatorNode::OP_SHIFT_RIGHT: priority = 4; break; - - case OperatorNode::OP_BIT_AND: priority = 5; break; - case OperatorNode::OP_BIT_XOR: priority = 6; break; - case OperatorNode::OP_BIT_OR: priority = 7; break; - - case OperatorNode::OP_LESS: priority = 8; break; - case OperatorNode::OP_LESS_EQUAL: priority = 8; break; - case OperatorNode::OP_GREATER: priority = 8; break; - case OperatorNode::OP_GREATER_EQUAL: priority = 8; break; - - case OperatorNode::OP_EQUAL: priority = 8; break; - case OperatorNode::OP_NOT_EQUAL: priority = 8; break; - - case OperatorNode::OP_IN: priority = 10; break; + p_variable->setter = parse_suite("setter definition"); + break; - case OperatorNode::OP_NOT: - priority = 11; - unary = true; - break; - case OperatorNode::OP_AND: priority = 12; break; - case OperatorNode::OP_OR: priority = 13; break; + 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_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; +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_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; - } + 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. + } +} - 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; - } - } +GDScriptParser::ConstantNode *GDScriptParser::parse_constant() { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) { + return nullptr; + } - if (next_op == -1) { + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->identifier = parse_identifier(); - _set_error("Yet another parser bug...."); - ERR_FAIL_V(nullptr); + if (match(GDScriptTokenizer::Token::COLON)) { + if (check((GDScriptTokenizer::Token::EQUAL))) { + // Infer type. + constant->infer_datatype = true; + } else { + // Parse type. + constant->datatype_specifier = parse_type(); } + } - // OK! create operator.. - if (is_unary) { - - int expr_pos = next_op; - while (expression[expr_pos].is_op) { - - expr_pos++; - if (expr_pos == expression.size()) { - //can happen.. - _set_error("Unexpected end of expression..."); - return nullptr; - } - } + if (consume(GDScriptTokenizer::Token::EQUAL, R"(Expected initializer after constant name.)")) { + // Initializer. + constant->initializer = parse_expression(false); - //consecutively do unary operators - for (int i = expr_pos - 1; i >= next_op; i--) { + if (constant->initializer == nullptr) { + push_error(R"(Expected initializer expression for constant.)"); + return nullptr; + } + } else { + return nullptr; + } - 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); - } + end_statement("constant declaration"); - } else if (is_ternary) { - if (next_op < 1 || next_op >= (expression.size() - 1)) { - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); - } + return constant; +} - 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; - } +GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name.)")) { + return nullptr; + } - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = expression[next_op].op; - op->line = op_line; //line might have been changed from a \n + ParameterNode *parameter = alloc_node<ParameterNode>(); + parameter->identifier = parse_identifier(); - if (expression[next_op - 1].is_op) { + 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(); + } + } - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); - } + if (match(GDScriptTokenizer::Token::EQUAL)) { + // Default value. + parameter->default_value = parse_expression(false); + } - 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 + return parameter; +} - _set_error("Unexpected two consecutive operators after ternary if."); - return nullptr; - } +GDScriptParser::SignalNode *GDScriptParser::parse_signal() { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) { + return nullptr; + } - if (expression[next_op + 3].is_op) { - // this is not invalid and can really appear - // but it becomes invalid anyway because no binary op - // can be followed by a unary op in a valid combination, - // due to how precedence works, unaries will always disappear first + SignalNode *signal = alloc_node<SignalNode>(); + signal->identifier = parse_identifier(); - _set_error("Unexpected two consecutive operators after ternary else."); - return nullptr; + if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + do { + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; } - 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 (next_op < 1 || next_op >= (expression.size() - 1)) { - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); + ParameterNode *parameter = parse_parameter(); + if (parameter == nullptr) { + push_error("Expected signal parameter name."); + break; } - - OperatorNode *op = alloc_node<OperatorNode>(); - op->op = expression[next_op].op; - op->line = op_line; //line might have been changed from a \n - - if (expression[next_op - 1].is_op) { - - _set_error("Parser bug..."); - ERR_FAIL_V(nullptr); + if (parameter->default_value != nullptr) { + push_error(R"(Signal parameters cannot have a default value.)"); } - - if (expression[next_op + 1].is_op) { - // this is not invalid and can really appear - // but it becomes invalid anyway because no binary op - // can be followed by a unary op in a valid combination, - // due to how precedence works, unaries will always disappear first - - _set_error("Unexpected two consecutive operators."); - return nullptr; + 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); } + } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); - op->arguments.push_back(expression[next_op - 1].node); //expression goes as left - op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right - - //replace all 3 nodes by this operator and make it an expression - expression.write[next_op - 1].node = op; - expression.remove(next_op); - expression.remove(next_op); - } + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); } - return expression[0].node; -} + end_statement("signal declaration"); -GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to_const) { + return signal; +} - switch (p_node->type) { +GDScriptParser::EnumNode *GDScriptParser::parse_enum() { + EnumNode *enum_node = alloc_node<EnumNode>(); + bool named = false; - case Node::TYPE_BUILT_IN_FUNCTION: { - //many may probably be optimizable - return p_node; - } break; - case Node::TYPE_ARRAY: { + if (check(GDScriptTokenizer::Token::IDENTIFIER)) { + advance(); + enum_node->identifier = parse_identifier(); + named = true; + } - ArrayNode *an = static_cast<ArrayNode *>(p_node); - bool all_constants = true; + push_multiline(true); + consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); - for (int i = 0; i < an->elements.size(); i++) { + HashMap<StringName, int> elements; - an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const); - if (an->elements[i]->type != Node::TYPE_CONSTANT) - all_constants = false; + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { + break; // Allow trailing comma. + } + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) { + EnumNode::Value item; + item.identifier = parse_identifier(); + item.parent_enum = enum_node; + item.line = previous.start_line; + item.leftmost_column = previous.leftmost_column; + item.rightmost_column = previous.rightmost_column; + + if (elements.has(item.identifier->name)) { + push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier); + } else if (!named) { + // TODO: Abstract this recursive member check. + ClassNode *parent = current_class; + while (parent != nullptr) { + 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; + } } - if (all_constants && p_to_const) { - //reduce constant array expression + elements[item.identifier->name] = item.line; - 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; + if (match(GDScriptTokenizer::Token::EQUAL)) { + ExpressionNode *value = parse_expression(false); + if (value == nullptr) { + push_error(R"(Expected expression value after "=".)"); } - cn->value = arr; - cn->datatype = _type_from_variant(cn->value); - return cn; + item.custom_value = value; } + item.rightmost_column = previous.rightmost_column; - return an; - - } break; - case Node::TYPE_DICTIONARY: { - - DictionaryNode *dn = static_cast<DictionaryNode *>(p_node); - bool all_constants = true; - - for (int i = 0; i < dn->elements.size(); i++) { - - dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const); - if (dn->elements[i].key->type != Node::TYPE_CONSTANT) - all_constants = false; - dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const); - if (dn->elements[i].value->type != Node::TYPE_CONSTANT) - all_constants = false; + item.index = enum_node->values.size(); + 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 (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); + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); - dict[key_c->value] = value_c->value; +#ifdef TOOLS_ENABLED + // Enum values documentaion. + for (int i = 0; i < enum_node->values.size(); i++) { + if (i == enum_node->values.size() - 1) { + // If close bracket is same line as last value. + if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) { + if (named) { + enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); + } else { + current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); } - cn->value = dict; - cn->datatype = _type_from_variant(cn->value); - return cn; } - - 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; + } else { + // If two values are same line. + if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) { + if (named) { + enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); + } else { + current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); } } + } + } +#endif // TOOLS_ENABLED - if (op->op == OperatorNode::OP_IS) { - //nothing much - return op; - } - 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) { + end_statement("enum"); - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { + return enum_node; +} - _set_error("Invalid argument (#" + itos(ce.argument + 1) + ") for " + errwhere + "."); +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; + } - } break; - case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: { + FunctionNode *function = alloc_node<FunctionNode>(); + make_completion_context(COMPLETION_OVERRIDE_METHOD, function); - _set_error("Too many arguments for " + errwhere + "."); - } break; - case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: { + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { + return nullptr; + } - _set_error("Too few arguments for " + errwhere + "."); - } break; - default: { - _set_error("Invalid arguments for " + errwhere + "."); + FunctionNode *previous_function = current_function; + current_function = function; - } break; - } + function->identifier = parse_identifier(); + function->is_static = _static; - error_line = op->line; + push_multiline(true); + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)"); - return p_node; - } + SuiteNode *body = alloc_node<SuiteNode>(); + SuiteNode *previous_suite = current_suite; + current_suite = body; - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = v; - cn->datatype = _type_from_variant(v); - return cn; + if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + bool default_used = false; + do { + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; + } + ParameterNode *parameter = parse_parameter(); + if (parameter == nullptr) { + break; + } + if (parameter->default_value != nullptr) { + default_used = true; + } else { + if (default_used) { + push_error("Cannot have a mandatory parameters after optional parameters."); + continue; } + } + if (function->parameters_indices.has(parameter->identifier->name)) { + push_error(vformat(R"(Parameter with name "%s" was already declared for this function.)", parameter->identifier->name)); + } else { + function->parameters_indices[parameter->identifier->name] = function->parameters.size(); + function->parameters.push_back(parameter); + body->add_local(parameter); + } + } while (match(GDScriptTokenizer::Token::COMMA)); + } - return op; //don't reduce yet - - } else if (op->op == OperatorNode::OP_YIELD) { - return op; + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*"); - } else if (op->op == OperatorNode::OP_INDEX) { - //can reduce indices into constant arrays or dictionaries + if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { + make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function); + function->return_type = parse_type(true); + if (function->return_type == nullptr) { + push_error(R"(Expected return type or "void" after "->".)"); + } + } - if (all_constants) { + // 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.)"); - ConstantNode *ca = static_cast<ConstantNode *>(op->arguments[0]); - ConstantNode *cb = static_cast<ConstantNode *>(op->arguments[1]); + current_suite = previous_suite; + function->body = parse_suite("function declaration", body); - bool valid; + current_function = previous_function; + return function; +} - Variant v = ca->value.get(cb->value, &valid); - if (!valid) { - _set_error("invalid index in constant expression"); - error_line = op->line; - return op; - } +GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_valid_targets) { + AnnotationNode *annotation = alloc_node<AnnotationNode>(); - ConstantNode *cn = alloc_node<ConstantNode>(); - cn->value = v; - cn->datatype = _type_from_variant(v); - return cn; - } + annotation->name = previous.literal; - return op; + make_completion_context(COMPLETION_ANNOTATION, annotation); - } else if (op->op == OperatorNode::OP_INDEX_NAMED) { + bool valid = true; - 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); } -} - -GDScriptParser::Node *GDScriptParser::_parse_and_reduce_expression(Node *p_parent, bool p_static, bool p_reduce_const, bool p_allow_assign) { - Node *expr = _parse_expression(p_parent, p_static, p_allow_assign, p_reduce_const); - if (!expr || error_set) - return nullptr; - expr = _reduce_expression(expr, p_reduce_const); - if (!expr || error_set) - return nullptr; - return expr; + return valid ? annotation : nullptr; } -bool GDScriptParser::_reduce_export_var_type(Variant &p_value, int p_line) { - - if (p_value.get_type() == Variant::ARRAY) { - Array arr = p_value; - for (int i = 0; i < arr.size(); i++) { - if (!_reduce_export_var_type(arr[i], p_line)) return false; - } - return true; - } - - if (p_value.get_type() == Variant::DICTIONARY) { - Dictionary dict = p_value; - for (int i = 0; i < dict.size(); i++) { - Variant value = dict.get_value_at_index(i); - if (!_reduce_export_var_type(value, p_line)) return false; - } - return true; +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); } - // validate type - DataType type = _type_from_variant(p_value); - if (type.kind == DataType::BUILTIN) { - return true; - } else if (type.kind == DataType::NATIVE) { - if (ClassDB::is_parent_class(type.native_type, "Resource")) { - return true; - } - } - _set_error("Invalid export type. Only built-in and native resource types can be exported.", p_line); - return false; + 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 (multiline) { + consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after %s.)", p_context)); - if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(); - break; - } - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && tokenizer->get_token(1) == GDScriptTokenizer::TK_PERIOD) { - // match everything - tokenizer->advance(2); - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pt_type = GDScriptParser::PatternNode::PT_IGNORE_REST; - pattern->array.push_back(sub_pattern); - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA && tokenizer->get_token(1) == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(2); - break; - } else if (tokenizer->get_token() == GDScriptTokenizer::TK_BRACKET_CLOSE) { - tokenizer->advance(1); - break; - } else { - _set_error("'..' pattern only allowed at the end of an array pattern"); - return nullptr; - } - } - - PatternNode *sub_pattern = _parse_pattern(p_static); - if (!sub_pattern) { - return nullptr; - } + 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 && 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 && result != nullptr) { + current_suite->has_unreachable_code = true; + if (current_function) { + push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name); + } else { + // TODO: Properties setters and getters with unreachable code are not being warned } + } #endif - 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. + assert->message = parse_expression(false); + if (assert->message == nullptr) { + push_error(R"(Expected error message for assert after ",".)"); + 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")"); + ContinueNode *cont = alloc_node<ContinueNode>(); + cont->is_for_match = is_continue_match; + return cont; } -void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) { - - const DataType &to_match_type = p_node_to_match->get_datatype(); - - switch (p_pattern->pt_type) { - case PatternNode::PT_CONSTANT: { - - DataType pattern_type = _reduce_node_type(p_pattern->constant); - if (error_set) { - return; - } - - OperatorNode *type_comp = nullptr; - - // static type check if possible - if (pattern_type.has_type && to_match_type.has_type) { - if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) { - _set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").", - p_pattern->line); - return; - } - } else { - // runtime typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>(); - typeof_pattern_value->op = OperatorNode::OP_CALL; - typeof_pattern_value->arguments.push_back(typeof_node); - typeof_pattern_value->arguments.push_back(p_pattern->constant); - - type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_pattern_value); - } - - // compare the actual values - OperatorNode *value_comp = alloc_node<OperatorNode>(); - value_comp->op = OperatorNode::OP_EQUAL; - value_comp->arguments.push_back(p_pattern->constant); - value_comp->arguments.push_back(p_node_to_match); - - if (type_comp) { - OperatorNode *full_comparison = alloc_node<OperatorNode>(); - full_comparison->op = OperatorNode::OP_AND; - full_comparison->arguments.push_back(type_comp); - full_comparison->arguments.push_back(value_comp); - - p_resulting_node = full_comparison; - } else { - p_resulting_node = value_comp; - } - - } break; - case PatternNode::PT_BIND: { - p_bindings[p_pattern->bind] = p_node_to_match; - - // a bind always matches - ConstantNode *true_value = alloc_node<ConstantNode>(); - true_value->value = Variant(true); - true_value->datatype = _type_from_variant(true_value->value); - p_resulting_node = true_value; - } break; - case PatternNode::PT_ARRAY: { - - bool open_ended = false; - - if (p_pattern->array.size() > 0) { - if (p_pattern->array[p_pattern->array.size() - 1]->pt_type == PatternNode::PT_IGNORE_REST) { - open_ended = true; - } - } - - // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() >= length - // typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length - - { - OperatorNode *type_comp = nullptr; - // static type check if possible - if (to_match_type.has_type) { - // must be an array - if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) { - _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line); - return; - } - } else { - // runtime typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_array = alloc_node<IdentifierNode>(); - typeof_array->name = "TYPE_ARRAY"; - - type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_array); - } - - // size - ConstantNode *length = alloc_node<ConstantNode>(); - length->value = Variant(open_ended ? p_pattern->array.size() - 1 : p_pattern->array.size()); - length->datatype = _type_from_variant(length->value); - - OperatorNode *call = alloc_node<OperatorNode>(); - call->op = OperatorNode::OP_CALL; - call->arguments.push_back(p_node_to_match); - - IdentifierNode *size = alloc_node<IdentifierNode>(); - size->name = "size"; - call->arguments.push_back(size); - - OperatorNode *length_comparison = alloc_node<OperatorNode>(); - length_comparison->op = open_ended ? OperatorNode::OP_GREATER_EQUAL : OperatorNode::OP_EQUAL; - length_comparison->arguments.push_back(call); - length_comparison->arguments.push_back(length); - - if (type_comp) { - OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>(); - type_and_length_comparison->op = OperatorNode::OP_AND; - type_and_length_comparison->arguments.push_back(type_comp); - type_and_length_comparison->arguments.push_back(length_comparison); - - p_resulting_node = type_and_length_comparison; - } else { - p_resulting_node = length_comparison; - } - } - - for (int i = 0; i < p_pattern->array.size(); i++) { - PatternNode *pattern = p_pattern->array[i]; - - Node *condition = nullptr; - - ConstantNode *index = alloc_node<ConstantNode>(); - index->value = Variant(i); - index->datatype = _type_from_variant(index->value); - - OperatorNode *indexed_value = alloc_node<OperatorNode>(); - indexed_value->op = OperatorNode::OP_INDEX; - indexed_value->arguments.push_back(p_node_to_match); - indexed_value->arguments.push_back(index); - - _generate_pattern(pattern, indexed_value, condition, p_bindings); - - // concatenate all the patterns with && - OperatorNode *and_node = alloc_node<OperatorNode>(); - and_node->op = OperatorNode::OP_AND; - and_node->arguments.push_back(p_resulting_node); - and_node->arguments.push_back(condition); - - p_resulting_node = and_node; - } - - } break; - case PatternNode::PT_DICTIONARY: { - - bool open_ended = false; +GDScriptParser::ForNode *GDScriptParser::parse_for() { + ForNode *n_for = alloc_node<ForNode>(); - if (p_pattern->array.size() > 0) { - open_ended = true; - } - - // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() >= length - // typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length - - { - OperatorNode *type_comp = nullptr; - // static type check if possible - if (to_match_type.has_type) { - // must be an dictionary - if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) { - _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line); - return; - } - } else { - // runtime typecheck - BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>(); - typeof_node->function = GDScriptFunctions::TYPE_OF; - - OperatorNode *typeof_match_value = alloc_node<OperatorNode>(); - typeof_match_value->op = OperatorNode::OP_CALL; - typeof_match_value->arguments.push_back(typeof_node); - typeof_match_value->arguments.push_back(p_node_to_match); - - IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>(); - typeof_dictionary->name = "TYPE_DICTIONARY"; - - type_comp = alloc_node<OperatorNode>(); - type_comp->op = OperatorNode::OP_EQUAL; - type_comp->arguments.push_back(typeof_match_value); - type_comp->arguments.push_back(typeof_dictionary); - } - - // size - ConstantNode *length = alloc_node<ConstantNode>(); - length->value = Variant(open_ended ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); - length->datatype = _type_from_variant(length->value); - - OperatorNode *call = alloc_node<OperatorNode>(); - call->op = OperatorNode::OP_CALL; - call->arguments.push_back(p_node_to_match); + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected loop variable name after "for".)")) { + n_for->variable = parse_identifier(); + } - IdentifierNode *size = alloc_node<IdentifierNode>(); - size->name = "size"; - call->arguments.push_back(size); + consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)"); - 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); + n_for->list = parse_expression(false); - 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::COLON, R"(Expected ":" after "for" condition.)"); - p_resulting_node = type_and_length_comparison; - } else { - p_resulting_node = length_comparison; - } - } + // Save break/continue state. + bool could_break = can_break; + bool could_continue = can_continue; + bool was_continue_match = is_continue_match; - for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) { + // Allow break/continue. + can_break = true; + can_continue = true; + is_continue_match = false; - Node *condition = nullptr; - - // check for has, then for pattern + SuiteNode *suite = alloc_node<SuiteNode>(); + if (n_for->variable) { + suite->add_local(SuiteNode::Local(n_for->variable)); + } + suite->parent_for = n_for; - IdentifierNode *has = alloc_node<IdentifierNode>(); - has->name = "has"; + n_for->loop = parse_suite(R"("for" block)", suite); - 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()); + // Reset break/continue state. + can_break = could_break; + can_continue = could_continue; + is_continue_match = was_continue_match; - if (e->value()) { + return n_for; +} - 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()); +GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { + IfNode *n_if = alloc_node<IfNode>(); - _generate_pattern(e->value(), indexed_value, condition, p_bindings); + n_if->condition = parse_expression(false); + if (n_if->condition == nullptr) { + push_error(vformat(R"(Expected conditional expression after "%s".)", p_token)); + } - 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); + consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token)); - condition = has_and_pattern; + n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token)); + n_if->true_block->parent_if = n_if; - } else { - condition = has_call; - } - - // concatenate all the patterns with && - OperatorNode *and_node = alloc_node<OperatorNode>(); - and_node->op = OperatorNode::OP_AND; - and_node->arguments.push_back(p_resulting_node); - and_node->arguments.push_back(condition); - - p_resulting_node = and_node; - } + 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); - true_value->datatype = _type_from_variant(true_value->value); - 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()); - local_var->assignments++; - - 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: { - - int line = tokenizer->get_token_line(); - - if (!_parse_newline()) { - if (!error_set) { - p_block->end_line = tokenizer->get_token_line(); - pending_newline = p_block->end_line; - } - return; - } + 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)); - _mark_line_as_safe(line); - NewLineNode *nl2 = alloc_node<NewLineNode>(); - nl2->line = line; - p_block->statements.push_back(nl2); + if (branch->patterns.is_empty()) { + push_error(R"(No pattern found for "match" branch.)"); + } - } 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) { + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); - _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. + // Save continue state. + bool could_continue = can_continue; + bool was_continue_match = is_continue_match; + // Allow continue for match. + can_continue = true; + is_continue_match = true; - tokenizer->advance(); - int var_line = tokenizer->get_token_line(); - if (!tokenizer->is_token_literal(0, true)) { + SuiteNode *suite = alloc_node<SuiteNode>(); + if (branch->patterns.size() > 0) { + List<StringName> binds; + branch->patterns[0]->binds.get_key_list(&binds); - _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; + is_continue_match = was_continue_match; - 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; + 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; } - - 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; + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); } - 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; + 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; - } - - int assert_line = tokenizer->get_token_line(); - - 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", assert_line); - return; - } - - AssertNode *an = alloc_node<AssertNode>(); - an->condition = _reduce_expression(args[0], p_static); - an->line = assert_line; - - if (args.size() == 2) { - an->message = _reduce_expression(args[1], p_static); - } else { - ConstantNode *message_node = alloc_node<ConstantNode>(); - message_node->value = String(); - message_node->datatype = _type_from_variant(message_node->value); - an->message = message_node; - } - - p_block->statements.push_back(an); - - if (!_end_statement()) { - _set_error("Expected end of statement after \"assert\".", assert_line); - 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(); - - 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; - } + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "while" condition.)"); - // see if inheritance happens from a file - if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) { + // Save break/continue state. + bool could_break = can_break; + bool could_continue = can_continue; + bool was_continue_match = is_continue_match; - Variant constant = tokenizer->get_token_constant(); - if (constant.get_type() != Variant::STRING) { + // Allow break/continue. + can_break = true; + can_continue = true; + is_continue_match = false; - _set_error("\"extends\" constant must be a string."); - return; - } + n_while->loop = parse_suite(R"("while" block)"); - p_class->extends_file = constant; - tokenizer->advance(); + // Reset break/continue state. + can_break = could_break; + can_continue = could_continue; + is_continue_match = was_continue_match; - // 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; - } - for (int i = 0; i < outer_class->variables.size(); i++) { - if (outer_class->variables[i].identifier == name) { - _set_error("A variable named \"" + String(name) + "\" already exists in the outer class scope (at line " + itos(outer_class->variables[i].line) + ")."); - return; - } - } - - outer_class = outer_class->owner; - } - - ClassNode *newclass = alloc_node<ClassNode>(); - newclass->initializer = alloc_node<BlockNode>(); - newclass->initializer->parent_class = newclass; - newclass->ready = alloc_node<BlockNode>(); - newclass->ready->parent_class = newclass; - newclass->name = name; - newclass->owner = p_class; - - p_class->subclasses.push_back(newclass); - - if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_EXTENDS) { - - _parse_extends(newclass); - if (error_set) - return; - } - - if (!_enter_indent_block()) { - - _set_error("Indented block expected."); - return; - } - current_class = newclass; - _parse_class(newclass); - current_class = p_class; - - } break; - /* this is for functions.... - case GDScriptTokenizer::TK_CF_PASS: { - - tokenizer->advance(1); - } break; - */ - case GDScriptTokenizer::TK_PR_STATIC: { - tokenizer->advance(); - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { - - _set_error("Expected \"func\"."); - return; - } - - [[fallthrough]]; - } - 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(vformat("Indented block expected after declaration of \"%s\" function.", function->name)); - return; - } - - function->return_type = return_type; - - if (_static) - p_class->static_functions.push_back(function); - else - p_class->functions.push_back(function); - - current_function = function; - function->body = block; - current_block = block; - _parse_block(block, _static); - current_block = nullptr; - - //arguments - } break; - case GDScriptTokenizer::TK_PR_SIGNAL: { - tokenizer->advance(); - - if (!tokenizer->is_token_literal()) { - _set_error("Expected an identifier after \"signal\"."); - return; - } - - ClassNode::Signal sig; - sig.name = tokenizer->get_token_identifier(); - sig.emissions = 0; - sig.line = tokenizer->get_token_line(); - 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" inside a 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) { - - _set_error("Can't accept a null constant expression for inferring export type."); - return; - } - - if (!_reduce_export_var_type(cn->value, member.line)) return; + } 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. + } - 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; - - Ref<GDScript> script; - StringName native; - ClassNode *base_class = nullptr; +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); - if (path != "") { - //path (and optionally subclasses) + current_function->is_coroutine = true; - 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++) { + return array; +} - String sub = p_class->extends_class[i]; - if (script->get_subclasses().has(sub)) { +GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign) { + DictionaryNode *dictionary = alloc_node<DictionaryNode>(); - Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing - script = subclass; - } else { - - _set_error("Couldn't find the subclass: " + sub, p_class->line); - return; - } - } + 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(); + if (key == nullptr) { + push_error(R"(Expected expression as dictionary key.)"); } - //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; - } - } - } - - 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(); - } - String name = script_type->get_name(); - if (name != String()) { - return name; + if (key != nullptr && value != nullptr) { + dictionary->elements.push_back({ key, value }); } - 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(); - } - - switch (tokenizer->get_token()) { - case GDScriptTokenizer::TK_PR_VOID: { - if (!p_can_be_void) { - return false; - } - r_type.kind = DataType::BUILTIN; - r_type.builtin_type = Variant::NIL; - } break; - case GDScriptTokenizer::TK_BUILT_IN_TYPE: { - r_type.builtin_type = tokenizer->get_token_type(); - if (tokenizer->get_token_type() == Variant::OBJECT) { - r_type.kind = DataType::NATIVE; - r_type.native_type = "Object"; - } else { - r_type.kind = DataType::BUILTIN; - } - } break; - case GDScriptTokenizer::TK_IDENTIFIER: { - r_type.native_type = tokenizer->get_token_identifier(); - if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) { - r_type.kind = DataType::NATIVE; - } else { - r_type.kind = DataType::UNRESOLVED; - can_index = true; - full_name = r_type.native_type; - } - } break; - default: { - return false; - } - } - - tokenizer->advance(); - - 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(); +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; +} - 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; - } - - StringName id; - bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id); - if (id == StringName()) { - id = "@temp"; - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) { + SubscriptNode *attribute = alloc_node<SubscriptNode>(); - full_name += "." + id.operator String(); - can_index = true; - if (has_completion) { - completion_cursor = full_name; - } - } break; - default: { - finished = true; - } break; + if (for_completion) { + bool is_builtin = false; + if (p_previous_operand->type == Node::IDENTIFIER) { + const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); + Variant::Type builtin_type = get_builtin_type(id->name); + if (builtin_type < Variant::VARIANT_MAX) { + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT, builtin_type, true); + is_builtin = true; } } - - if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) { - _set_error("Expected a subclass identifier."); - return false; + if (!is_builtin) { + make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true); } - - r_type.native_type = full_name; } - return true; -} - -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; + attribute->is_attribute = true; + attribute->base = p_previous_operand; - Vector<String> full_name = p_source.native_type.operator String().split(".", false); - int name_part = 0; - - DataType result; - result.has_type = true; + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { + return attribute; + } + attribute->attribute = parse_identifier(); - while (name_part < full_name.size()) { + return attribute; +} - bool found = false; - StringName id = full_name[name_part]; - DataType base_type = result; +GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) { + SubscriptNode *subscript = alloc_node<SubscriptNode>(); - ClassNode *p = nullptr; - if (name_part == 0) { - if (ScriptServer::is_global_class(id)) { - String script_path = ScriptServer::get_global_class_path(id); - if (script_path == self_path) { - result.kind = DataType::CLASS; - result.class_type = static_cast<ClassNode *>(head); - } else { - Ref<Script> script = ResourceLoader::load(script_path); - Ref<GDScript> gds = script; - if (gds.is_valid()) { - if (!gds->is_valid()) { - _set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line); - return DataType(); - } - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - } else if (script.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = script; - } else { - _set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line); - return DataType(); - } - } - name_part++; - continue; - } - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); - String singleton_path; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; - } - String name = s.get_slice("/", 1); - if (name == id) { - singleton_path = ProjectSettings::get_singleton()->get(s); - if (singleton_path.begins_with("*")) { - singleton_path = singleton_path.right(1); - } - if (!singleton_path.begins_with("res://")) { - singleton_path = "res://" + singleton_path; - } - break; - } - } - if (!singleton_path.empty()) { - Ref<Script> script = ResourceLoader::load(singleton_path); - Ref<GDScript> gds = script; - if (gds.is_valid()) { - if (!gds->is_valid()) { - _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line); - return DataType(); - } - result.kind = DataType::GDSCRIPT; - result.script_type = gds; - } else if (script.is_valid()) { - result.kind = DataType::SCRIPT; - result.script_type = script; - } else { - _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line); - return DataType(); - } - name_part++; - continue; - } + make_completion_context(COMPLETION_SUBSCRIPT, subscript); - p = current_class; - } else if (base_type.kind == DataType::CLASS) { - p = base_type.class_type; - } - 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; - } + subscript->base = p_previous_operand; + subscript->index = parse_expression(false); - // 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; - } + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)"); - if (!found && p->base_type.kind == DataType::CLASS) { - p = p->base_type.class_type; - } else { - base_type = p->base_type; - break; - } - } + return subscript; +} - // 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(); - } - } - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) { + CastNode *cast = alloc_node<CastNode>(); - if (!found && !for_completion) { - String base; - if (name_part == 0) { - base = "self"; - } else { - base = result.to_string(); - } - _set_error("The identifier \"" + String(id) + "\" isn't a valid type (not a script or class), or couldn't be found on base \"" + - base + "\".", - p_line); - return DataType(); - } + cast->operand = p_previous_operand; + cast->cast_type = parse_type(); - name_part++; + if (cast->cast_type == nullptr) { + push_error(R"(Expected type specifier after "as".)"); + return p_previous_operand; } - return result; + return cast; } -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(); - - 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; +GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) { + CallNode *call = alloc_node<CallNode>(); + + if (previous.type == GDScriptTokenizer::Token::SUPER) { + // Super call. + call->is_super = true; + push_multiline(true); + if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + // Implicit call to the parent method of the same name. + if (current_function == nullptr) { + push_error(R"(Cannot use implicit "super" call outside of a function.)"); + pop_multiline(); + return nullptr; + } + call->function_name = current_function->identifier->name; } else { - result.is_meta_type = false; - scr = obj->get_script(); + consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); + make_completion_context(COMPLETION_SUPER_METHOD, call, true); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) { + pop_multiline(); + return nullptr; + } + IdentifierNode *identifier = parse_identifier(); + call->callee = identifier; + call->function_name = identifier->name; + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after function name.)"); } - if (scr.is_valid()) { - result.script_type = scr; - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = DataType::GDSCRIPT; + } 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 { - result.kind = DataType::SCRIPT; + // 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.)*"); } - result.native_type = scr->get_instance_base_type(); } else { - result.kind = DataType::NATIVE; + push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*"); } } - 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; + // Arguments. + CompletionType ct = COMPLETION_CALL_ARGUMENTS; + if (call->function_name == "load") { + ct = COMPLETION_RESOURCE_PATH; } - ret.has_type = true; - ret.builtin_type = p_property.type; - if (p_property.type == Variant::OBJECT) { - ret.kind = DataType::NATIVE; - ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; - } else { - ret.kind = DataType::BUILTIN; - } - return ret; -} - -GDScriptParser::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; - - 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; -} - -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(); - } - - 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; - - Variant a; - REF a_ref; - if (a_type == Variant::OBJECT) { - a_ref.instance(); - a = a_ref; - } else { - Callable::CallError err; - a = Variant::construct(a_type, nullptr, 0, err); - if (err.error != Callable::CallError::CALL_OK) { - r_valid = false; - return DataType(); + push_completion_call(call); + int argument_index = 0; + do { + make_completion_context(ct, call, argument_index++, true); + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; } - } - 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(); + ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { + push_error(R"(Expected expression as the function argument.)"); + } else { + call->arguments.push_back(argument); } - } + ct = COMPLETION_CALL_ARGUMENTS; + } while (match(GDScriptTokenizer::Token::COMMA)); + pop_completion_call(); - // 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_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*"); - if (r_valid) { - return _type_from_variant(ret); - } - - return DataType(); + return call; } -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; +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 identifier after "$".)"); + return nullptr; } - case OperatorNode::OP_ASSIGN_SHIFT_RIGHT: - case OperatorNode::OP_SHIFT_RIGHT: { - return Variant::OP_SHIFT_RIGHT; - } - default: { - return Variant::OP_MAX; - } break; + GetNodeNode *get_node = alloc_node<GetNodeNode>(); + make_completion_context(COMPLETION_GET_NODE, get_node); + get_node->string = parse_literal(); + return get_node; + } else if (current.is_node_name()) { + GetNodeNode *get_node = alloc_node<GetNodeNode>(); + int chain_position = 0; + do { + make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); + if (!current.is_node_name()) { + push_error(R"(Expect node path after "/".)"); + return nullptr; + } + advance(); + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = previous.get_identifier(); + get_node->chain.push_back(identifier); + } while (match(GDScriptTokenizer::Token::SLASH)); + return get_node; + } else { + push_error(R"(Expect node path as string or identifier after "$".)"); + return nullptr; } } -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; - } +GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign) { + PreloadNode *preload = alloc_node<PreloadNode>(); + preload->resolved_path = "<missing path>"; - if (p_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; - } + push_multiline(true); + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)"); - 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; - } - - // 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; - } + make_completion_context(COMPLETION_RESOURCE_PATH, preload); + push_completion_call(preload); - StringName expr_native; - Ref<Script> expr_script; - ClassNode *expr_class = nullptr; + preload->path = parse_expression(false); - switch (p_expression.kind) { - case DataType::NATIVE: { - if (p_container.kind != DataType::NATIVE) { - // Non-native type can't be a superclass of a native type - return false; - } - if (p_expression.is_meta_type) { - expr_native = GDScriptNativeClass::get_class_static(); - } else { - expr_native = p_expression.native_type; - } - } break; - case DataType::SCRIPT: - case DataType::GDSCRIPT: { - if (p_container.kind == DataType::CLASS) { - // This cannot be resolved without cyclic dependencies, so just bail out - return false; - } - if (p_expression.is_meta_type) { - expr_native = p_expression.script_type->get_class_name(); - } else { - expr_script = p_expression.script_type; - expr_native = expr_script->get_instance_base_type(); - } - } break; - case DataType::CLASS: { - if (p_expression.is_meta_type) { - expr_native = GDScript::get_class_static(); - } else { - expr_class = p_expression.class_type; - ClassNode *base = expr_class; - while (base->base_type.kind == DataType::CLASS) { - base = base->base_type.class_type; - } - expr_native = base->base_type.native_type; - expr_script = base->base_type.script_type; - } - } break; - case DataType::BUILTIN: // Already handled above - case DataType::UNRESOLVED: // Not allowed, see above - break; + if (preload->path == nullptr) { + push_error(R"(Expected resource path after "(".)"); } - // Some classes are prefixed with `_` internally - if (!ClassDB::class_exists(expr_native)) { - expr_native = "_" + expr_native; - } + pop_completion_call(); - 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; - } + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*"); - return false; + return preload; } -GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) { - Node *result; +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; - if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) { - if (p_type.builtin_type == Variant::ARRAY) { - result = alloc_node<ArrayNode>(); - } else if (p_type.builtin_type == Variant::DICTIONARY) { - result = alloc_node<DictionaryNode>(); - } else { - ConstantNode *c = alloc_node<ConstantNode>(); - Callable::CallError err; - c->value = Variant::construct(p_type.builtin_type, nullptr, 0, err); - c->datatype = _type_from_variant(c->value); - result = c; - } - } else { - ConstantNode *c = alloc_node<ConstantNode>(); - c->value = Variant(); - c->datatype = _type_from_variant(c->value); - result = c; + 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. } - result->line = p_line; - - return result; + // Return the previous expression. + return p_previous_operand; } -GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) { -#ifdef DEBUG_ENABLED - if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) { -#else - if (p_node->get_datatype().has_type) { -#endif - return p_node->get_datatype(); - } - - DataType node_type; - - 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]; +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 { - node_type = _reduce_identifier_type(nullptr, id->name, id->line, false); + push_error(R"("void" is only allowed for a function return type.)"); } - } break; - case Node::TYPE_CAST: { - CastNode *cn = static_cast<CastNode *>(p_node); - - DataType source_type = _reduce_node_type(cn->source_node); - cn->cast_type = _resolve_type(cn->cast_type, cn->line); - if (source_type.has_type) { - - bool valid = false; - if (check_types) { - if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) { - valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type); - } - if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) { - valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type); - } - - if (!valid) { - _set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() + - "\" to \"" + cn->cast_type.to_string() + "\".", - cn->line); - return DataType(); - } - } - } 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) { - _mark_line_as_unsafe(op->line); - _add_warning(GDScriptWarning::UNSAFE_PROPERTY_ACCESS, op->line, member_id->name.operator String(), base_type.to_string()); - } -#endif // DEBUG_ENABLED - } - } 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); - } - } + } + // Leave error message to the caller who knows the context. + return nullptr; + } - DataType base_type = _reduce_node_type(op->arguments[0]); - DataType index_type = _reduce_node_type(op->arguments[1]); + IdentifierNode *type_element = parse_identifier(); - if (!base_type.has_type) { - _mark_line_as_unsafe(op->line); - break; - } + type->type_chain.push_back(type_element); - 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()); - } - } - } break; - default: { + 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); } } - node_type = _resolve_type(node_type, p_node->line); - p_node->set_datatype(node_type); - return node_type; + return type; } -bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const { - - r_static = false; - r_default_arg_count = 0; - - DataType original_type = p_base_type; - ClassNode *base = nullptr; - FunctionNode *callee = nullptr; +#ifdef TOOLS_ENABLED +static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) { + int start_block = p_line.rfind("[codeblock]"); + int end_block = p_line.rfind("[/codeblock]"); - if (p_base_type.kind == DataType::CLASS) { - base = p_base_type.class_type; + if (start_block != -1 && r_block_begins) { + *r_block_begins = start_block; } - // 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; + if (p_already_in) { + if (end_block == -1) { + return true; + } else if (start_block == -1) { + return false; } else { - break; + return start_block > end_block; } - } - - 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; - } - - // 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 + } else { + if (start_block == -1) { 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; - } - - while (base_gdscript.is_valid()) { - native = base_gdscript->get_instance_base_type(); - - Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions(); - - 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))); - } + } else if (end_block == -1) { return true; + } else { + return start_block > end_block; } - - 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); +bool GDScriptParser::has_comment(int p_line) { + return tokenizer.get_comments().has(p_line); +} - if (!(mi == MethodInfo())) { - r_return_type = _type_from_property(mi.return_val, false); - r_default_arg_count = mi.default_arguments.size(); - for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) { - r_arg_types.push_back(_type_from_property(E->get())); - } - return true; +String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { + const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + ERR_FAIL_COND_V(!comments.has(p_line), String()); + + if (p_single_line) { + if (comments[p_line].comment.begins_with("##")) { + return comments[p_line].comment.trim_prefix("##").strip_edges(); } - base_script = base_script->get_base_script(); + return ""; } - if (native == StringName()) { - // Empty native class, might happen in some Script implementations - // Just ignore it - return false; - } + String doc; -#ifdef DEBUG_METHODS_ENABLED + int line = p_line; + bool in_codeblock = false; - // 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."); + while (comments.has(line - 1)) { + if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { + break; + } + line--; } - MethodBind *method = ClassDB::get_method(native, p_function); - - if (!method) { - // Try virtual methods - List<MethodInfo> virtuals; - ClassDB::get_virtual_methods(native, &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_vararg = mi.flags & METHOD_FLAG_VARARG; - return true; - } + int codeblock_begins = 0; + while (comments.has(line)) { + if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { + break; } + String doc_line = comments[line].comment.trim_prefix("##"); - // 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); + in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); - 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; - } + if (in_codeblock) { + int i = 0; + for (; i < codeblock_begins; i++) { + if (doc_line[i] != ' ') { + break; } - return false; } + doc_line = doc_line.substr(i); } else { - return false; + doc_line = doc_line.strip_edges(); } - } + String line_join = (in_codeblock) ? "\n" : " "; - 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))); + doc = (doc.is_empty()) ? doc_line : doc + line_join + doc_line; + line++; } - return true; -#else - return false; -#endif + + return doc; } -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()); +void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) { + const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + if (!comments.has(p_line)) { + return; } - - 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(); - } - 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]); + ERR_FAIL_COND(p_brief != "" || p_desc != "" || p_tutorials.size() != 0); + + int line = p_line; + bool in_codeblock = false; + enum Mode { + BRIEF, + DESC, + TUTORIALS, + DONE, + }; + Mode mode = BRIEF; + + if (p_inner_class) { + while (comments.has(line - 1)) { + if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { + break; } + line--; + } + } - // 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 codeblock_begins = 0; + while (comments.has(line)) { + if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { + break; + } - int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0; + String title, link; // For tutorials. + String doc_line = comments[line++].comment.trim_prefix("##"); + String striped_line = doc_line.strip_edges(); - 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()); - } + // Set the read mode. + if (striped_line.begins_with("@desc:") && p_desc == "") { + mode = DESC; + striped_line = striped_line.trim_prefix("@desc:"); + in_codeblock = _in_codeblock(doc_line, in_codeblock); - // 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]); + } else if (striped_line.begins_with("@tutorial")) { + int begin_scan = String("@tutorial").length(); + if (begin_scan >= striped_line.length()) { + continue; // invalid syntax. } - IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]); - callee_name = func_id->name; - arg_count -= 1 + arg_id; + if (striped_line[begin_scan] == ':') { // No title. + // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. + title = ""; + link = striped_line.trim_prefix("@tutorial:").strip_edges(); - 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); - } + /* Syntax: + @tutorial ( The Title Here ) : http://the.url/ + ^ open ^ close ^ colon ^ url + */ + int open_bracket_pos = begin_scan, close_bracket_pos = 0; + while (open_bracket_pos < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) { + open_bracket_pos++; } - - 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; + if (open_bracket_pos == striped_line.length() || striped_line[open_bracket_pos++] != '(') { + continue; // invalid syntax. } - break; - } - - DataType original_type = base_type; - bool is_initializer = callee_name == "new"; - bool is_static = false; - bool valid = false; - - if (is_initializer && original_type.is_meta_type) { - // Try to check it as initializer - base_type = original_type; - callee_name = "_init"; - base_type.is_meta_type = false; - - valid = _get_function_signature(base_type, callee_name, return_type, arg_types, - default_args_count, is_static, is_vararg); - - return_type = original_type; - return_type.is_meta_type = false; - - valid = true; // There's always an initializer, we can assume this is true - } - - if (!valid) { - base_type = original_type; - return_type = DataType(); - valid = _get_function_signature(base_type, callee_name, return_type, arg_types, - default_args_count, is_static, is_vararg); - } - - if (!valid) { -#ifdef DEBUG_ENABLED - if (p_call->arguments[0]->type == Node::TYPE_SELF) { - _set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line); - return DataType(); + close_bracket_pos = open_bracket_pos; + while (close_bracket_pos < striped_line.length() && striped_line[close_bracket_pos] != ')') { + close_bracket_pos++; } - 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()); - } + if (close_bracket_pos == striped_line.length()) { + continue; // invalid syntax. } - _add_warning(GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->line, callee_name, original_type.to_string()); - _mark_line_as_unsafe(p_call->line); -#endif // DEBUG_ENABLED - return DataType(); - } - -#ifdef DEBUG_ENABLED - if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) { - _set_error("Can't call non-static function from a static function.", p_call->line); - return DataType(); - } - - if (check_types && !is_static && !is_initializer && base_type.is_meta_type) { - _set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line); - return DataType(); - } - // Check signal emission for warnings - if (callee_name == "emit_signal" && p_call->op == OperatorNode::OP_CALL && p_call->arguments[0]->type == Node::TYPE_SELF && p_call->arguments.size() >= 3 && p_call->arguments[2]->type == Node::TYPE_CONSTANT) { - ConstantNode *sig = static_cast<ConstantNode *>(p_call->arguments[2]); - String emitted = sig->value.get_type() == Variant::STRING ? sig->value.operator String() : ""; - for (int i = 0; i < current_class->_signals.size(); i++) { - if (current_class->_signals[i].name == emitted) { - current_class->_signals.write[i].emissions += 1; - break; - } + int colon_pos = close_bracket_pos + 1; + while (colon_pos < striped_line.length() && (striped_line[colon_pos] == ' ' || striped_line[colon_pos] == '\t')) { + colon_pos++; + } + if (colon_pos == striped_line.length() || striped_line[colon_pos++] != ':') { + continue; // invalid syntax. } - } -#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]); + title = striped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); + link = striped_line.substr(colon_pos).strip_edges(); + } - if ((i - arg_diff) >= arg_types.size()) { + mode = TUTORIALS; + in_codeblock = false; + } else if (striped_line.is_empty()) { 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]))); - } - } 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); - } } 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); + // Tutorial docs are single line, we need a @tag after it. + if (mode == TUTORIALS) { + mode = DONE; } - } - } -#endif // DEBUG_ENABLED - - return return_type; -} - -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; + in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); } - if (!base_type.is_meta_type) { - for (int i = 0; i < base->variables.size(); i++) { - if (base->variables[i].identifier == p_member) { - r_member_type = base->variables[i].data_type; - base->variables.write[i].usages += 1; - return true; + if (in_codeblock) { + int i = 0; + for (; i < codeblock_begins; i++) { + if (doc_line[i] != ' ') { + break; } } + doc_line = doc_line.substr(i); } 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; - } - } + doc_line = striped_line; } + String line_join = (in_codeblock) ? "\n" : " "; - base_type = base->base_type; - if (base_type.kind == DataType::CLASS) { - base = base_type.class_type; - } else { - break; + switch (mode) { + case BRIEF: + p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line; + break; + case DESC: + p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line; + break; + case TUTORIALS: + p_tutorials.append(Pair<String, String>(title, link)); + break; + case DONE: + return; } } +} +#endif // TOOLS_ENABLED + +GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) { + // Function table for expression parsing. + // clang-format destroys the alignment here, so turn off for the table. + /* 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_SUBTRACTION }, // PLUS, + { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION_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]; +} - 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; - } +bool GDScriptParser::SuiteNode::has_local(const StringName &p_name) const { + if (locals_indices.has(p_name)) { + return true; } - - Ref<Script> scr; - if (base_type.kind == DataType::SCRIPT) { - scr = base_type.script_type; + if (parent_block != nullptr) { + return parent_block->has_local(p_name); } + return false; +} - StringName native; - if (base_type.kind == DataType::NATIVE) { - native = base_type.native_type; +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]]; } - - // 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; - } + if (parent_block != nullptr) { + return parent_block->get_local(p_name); } + return empty; +} -#define IS_USAGE_MEMBER(m_usage) (!(m_usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY))) +bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const { + return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); +} - // 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; - } +bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { + return (info->target_kind & p_target_kinds) > 0; +} - List<PropertyInfo> properties; - scr->get_script_property_list(&properties); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) { - r_member_type = _type_from_property(E->get()); - return true; - } - } +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)); - base_type = _type_from_variant(scr.operator Variant()); - native = scr->get_instance_base_type(); - scr = scr->get_base_script(); - } + const MethodInfo &info = valid_annotations[p_annotation->name].info; - if (native == StringName()) { - // Empty native class, might happen in some Script implementations - // Just ignore it + 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; } - // 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."); + 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; } - 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; - } + 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 (!base_type.is_meta_type) { - List<PropertyInfo> properties; - ClassDB::get_property_list(native, &properties); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) { - // Check if a getter exists - StringName getter_name = ClassDB::get_property_getter(native, p_member); - if (getter_name != StringName()) { - // Use the getter return type -#ifdef DEBUG_METHODS_ENABLED - MethodBind *getter_method = ClassDB::get_method(native, getter_name); - if (getter_method) { - r_member_type = _type_from_property(getter_method->get_return_info()); - } else { - r_member_type = DataType(); - } -#else - r_member_type = DataType(); -#endif - } else { - r_member_type = _type_from_property(E->get()); - } - return true; - } + if (E->next() != nullptr) { + E = E->next(); } - } - // If the base is a script, it might be trying to access members of the Script class itself - if (p_base_type.is_meta_type && (p_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) { - native = p_base_type.script_type->get_class_name(); - ClassDB::get_integer_constant(native, p_member, &valid); - if (valid) { - DataType ct; - ct.has_type = true; - ct.is_constant = true; - ct.kind = DataType::BUILTIN; - ct.builtin_type = Variant::INT; - r_member_type = ct; - return true; - } - - List<PropertyInfo> properties; - ClassDB::get_property_list(native, &properties); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { - if (E->get().name == p_member && IS_USAGE_MEMBER(E->get().usage)) { - // Check if a getter exists - StringName getter_name = ClassDB::get_property_getter(native, p_member); - if (getter_name != StringName()) { - // Use the getter return type -#ifdef DEBUG_METHODS_ENABLED - MethodBind *getter_method = ClassDB::get_method(native, getter_name); - if (getter_method) { - r_member_type = _type_from_property(getter_method->get_return_info()); - } else { - r_member_type = DataType(); + 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(); + Variant r; + Variant::construct(parameter.type, r, &(name), 1, error); + p_annotation->resolved_arguments.push_back(r); + if (error.error != Callable::CallError::CALL_OK) { + push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + return false; } -#else - r_member_type = DataType(); -#endif - } else { - r_member_type = _type_from_property(E->get()); + 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; } - return true; + + 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; + Variant r; + Variant::construct(parameter.type, r, &(args), 1, error); + p_annotation->resolved_arguments.push_back(r); + if (error.error != Callable::CallError::CALL_OK) { + push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); + p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + return false; + } + break; } } } -#undef IS_USAGE_MEMBER - return false; + return true; } -GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line, bool p_is_indexing) { +bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { + this->_is_tool = true; + return true; +} - if (p_base_type && !p_base_type->has_type) { - return DataType(); - } +bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); + ClassNode *p_class = static_cast<ClassNode *>(p_node); + p_class->icon_path = p_annotation->resolved_arguments[0]; + return true; +} - DataType base_type; - DataType member_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.)"); - if (!p_base_type) { - base_type.has_type = true; - base_type.is_constant = true; - base_type.kind = DataType::CLASS; - base_type.class_type = current_class; - } else { - base_type = DataType(*p_base_type); + 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 (_get_member_type(base_type, p_identifier, member_type)) { - return member_type; - } +template <PropertyHint t_hint, Variant::Type t_type> +bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); - if (p_is_indexing) { - // Don't look for globals since this is an indexed identifier - return DataType(); + 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; } - 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; - } - _set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded."); - return DataType(); - } - - if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier]; - Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - return _type_from_variant(g); - } - - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) { - Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier]; - return _type_from_variant(g); - } - - // Non-tool singletons aren't loaded, check project settings - List<PropertyInfo> props; - ProjectSettings::get_singleton()->get_property_list(&props); + 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; - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; - if (!s.begins_with("autoload/")) { - continue; + 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; } - 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; - } - } + 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; } - } - - // 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); + 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. } -#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()); + 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]); } -#endif // DEBUG_ENABLED - _mark_line_as_unsafe(p_line); - return DataType(); -} + variable->export_info.hint_string = hint_string; -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; + return true; +} - _mark_line_as_safe(p_class->line); +bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_V_MSG(false, "Not implemented."); +} - // 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); +template <MultiplayerAPI::RPCMode t_mode> +bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - if (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; + 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; } - - expr.is_constant = true; - c.type = expr; - c.expression->set_datatype(expr); - - 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; + case Node::FUNCTION: { + FunctionNode *function = static_cast<FunctionNode *>(p_node); + if (function->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + push_error(R"(RPC annotations can only be used once per function.)", p_annotation); + } + function->rpc_mode = t_mode; + break; } + default: + return false; // Unreachable. } - // 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; - } + return true; +} - for (int i = 0; i < p_class->functions.size(); i++) { - _check_function_types(p_class->functions[i]); - if (error_set) return; +GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const { + switch (type) { + case CONSTANT: + return constant->get_datatype(); + case VARIABLE: + return variable->get_datatype(); + case PARAMETER: + return parameter->get_datatype(); + case FOR_VARIABLE: + case PATTERN_BIND: + return bind->get_datatype(); + case UNDEFINED: + return DataType(); } + return DataType(); +} - // 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); +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; +} - v.expression = convert_call; - v.initial_assignment->arguments.write[1] = convert_call; - } +String GDScriptParser::DataType::to_string() const { + switch (kind) { + case VARIANT: + return "Variant"; + case BUILTIN: + if (builtin_type == Variant::NIL) { + return "null"; } - - 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; + return Variant::get_type_name(builtin_type); + case NATIVE: + if (is_meta_type) { + return GDScriptNativeClass::get_class_static(); } - } - - // 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; + return native_type.operator String(); + case CLASS: + if (is_meta_type) { + return GDScript::get_class_static(); } - } - - // 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 (class_type->identifier != nullptr) { + return class_type->identifier->name.operator String(); } - 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; - } + return class_type->fqcn; + case SCRIPT: { + if (is_meta_type) { + return script_type->get_class_name().operator String(); } - if (found_getter && found_setter) break; - } - - if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue; - - // 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; + String name = script_type->get_name(); + if (!name.is_empty()) { + return name; } - 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; + name = script_path; + if (!name.is_empty()) { + return name; } + return native_type.operator String(); } - - if (!found_setter && v.setter != StringName()) { - _set_error("The setter function isn't defined.", v.line); - return; - } - - if (!found_getter && v.getter != StringName()) { - _set_error("The getter function isn't defined.", v.line); - return; - } + case ENUM: + return enum_type.operator String() + " (enum)"; + case ENUM_VALUE: + return enum_type.operator String() + " (enum value)"; + case UNRESOLVED: + return "<unresolved type>"; } - // 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; - } + ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range."); } -void GDScriptParser::_check_function_types(FunctionNode *p_function) { +/*---------- PRETTY PRINT FOR DEBUG ----------*/ - p_function->return_type = _resolve_type(p_function->return_type, p_function->line); +#ifdef DEBUG_ENABLED - // 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); +void GDScriptParser::TreePrinter::increase_indent() { + indent_level++; + indent = ""; + for (int i = 0; i < indent_level * 4; i++) { + if (i % 4 == 0) { + indent += "|"; } 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()); + indent += " "; } - 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::decrease_indent() { + indent_level--; + indent = ""; + for (int i = 0; i < indent_level * 4; i++) { + if (i % 4 == 0) { + indent += "|"; + } else { + indent += " "; } -#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++]; - } +void GDScriptParser::TreePrinter::push_line(const String &p_line) { + if (!p_line.is_empty()) { + push_text(p_line); + } + printed += "\n"; + pending_indent = true; +} - 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::push_text(const String &p_text) { + if (pending_indent) { + printed += indent; + pending_indent = false; + } + printed += p_text; +} - j++; - } - } - parent_signature += ")"; - _set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line); - return; - } - } -#endif // DEBUG_ENABLED - } else { - if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) { - _set_error("The constructor can't return a value.", p_function->line); - return; +void GDScriptParser::TreePrinter::print_annotation(AnnotationNode *p_annotation) { + push_text(p_annotation->name); + push_text(" ("); + for (int i = 0; i < p_annotation->arguments.size(); i++) { + if (i > 0) { + push_text(" , "); } + print_expression(p_annotation->arguments[i]); } + push_line(")"); +} - if (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; +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(" ]"); +} + +void GDScriptParser::TreePrinter::print_assert(AssertNode *p_assert) { + push_text("Assert ( "); + print_expression(p_assert->condition); + push_line(" )"); +} - 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; +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. } -#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)); - } - } + 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; } -#endif // DEBUG_ENABLED + push_text("= "); + print_expression(p_assignment->assigned_value); + push_line(); } -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_await(AwaitNode *p_await) { + push_text("Await "); + print_expression(p_await->to_await); +} - 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; +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(")"); +} -#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); +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); } - for (int i = 0; i < p_class->_signals.size(); i++) { - if (p_class->_signals[i].emissions == 0) { - _add_warning(GDScriptWarning::UNUSED_SIGNAL, p_class->_signals[i].line, p_class->_signals[i].name); + push_text("( "); + for (int i = 0; i < p_call->arguments.size(); i++) { + if (i > 0) { + push_text(" , "); } + print_expression(p_call->arguments[i]); } -#endif // DEBUG_ENABLED - - // 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; - } + push_text(" )"); } -#ifdef DEBUG_ENABLED -static String _find_function_name(const GDScriptParser::OperatorNode *p_call) { - switch (p_call->arguments[0]->type) { - case GDScriptParser::Node::TYPE_TYPE: { - return Variant::get_type_name(static_cast<GDScriptParser::TypeNode *>(p_call->arguments[0])->vtype); - } break; - case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: { - return GDScriptFunctions::get_func_name(static_cast<GDScriptParser::BuiltInFunctionNode *>(p_call->arguments[0])->function); - } break; - default: { - int id_index = p_call->op == GDScriptParser::OperatorNode::OP_PARENT_CALL ? 0 : 1; - if (p_call->arguments.size() > id_index && p_call->arguments[id_index]->type == GDScriptParser::Node::TYPE_IDENTIFIER) { - return static_cast<GDScriptParser::IdentifierNode *>(p_call->arguments[id_index])->name; - } - } break; - } - return String(); +void GDScriptParser::TreePrinter::print_cast(CastNode *p_cast) { + print_expression(p_cast->operand); + push_text(" AS "); + print_type(p_cast->cast_type); } -#endif // DEBUG_ENABLED - -void GDScriptParser::_check_block_types(BlockNode *p_block) { - - Node *last_var_assign = nullptr; - - // Check each statement - for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) { - Node *statement = E->get(); - switch (statement->type) { - case Node::TYPE_NEWLINE: - case Node::TYPE_BREAKPOINT: { - // Nothing to do - } break; - case Node::TYPE_ASSERT: { - AssertNode *an = static_cast<AssertNode *>(statement); - _mark_line_as_safe(an->line); - _reduce_node_type(an->condition); - _reduce_node_type(an->message); - } break; - case Node::TYPE_LOCAL_VAR: { - LocalVarNode *lv = static_cast<LocalVarNode *>(statement); - lv->datatype = _resolve_type(lv->datatype, lv->line); - _mark_line_as_safe(lv->line); - - last_var_assign = lv->assign; - if (lv->assign) { - lv->assign_op->arguments[0]->set_datatype(lv->datatype); - DataType assign_type = _reduce_node_type(lv->assign); -#ifdef DEBUG_ENABLED - if (assign_type.has_type && assign_type.kind == DataType::BUILTIN && assign_type.builtin_type == Variant::NIL) { - if (lv->assign->type == Node::TYPE_OPERATOR) { - OperatorNode *call = static_cast<OperatorNode *>(lv->assign); - if (call->op == OperatorNode::OP_CALL || call->op == OperatorNode::OP_PARENT_CALL) { - _add_warning(GDScriptWarning::VOID_ASSIGNMENT, lv->line, _find_function_name(call)); - } - } - } - if (lv->datatype.has_type && assign_type.may_yield && lv->assign->type == Node::TYPE_OPERATOR) { - _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, lv->line, _find_function_name(static_cast<OperatorNode *>(lv->assign))); - } -#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; - tgt_type->datatype = _type_from_variant(tgt_type->value); - - OperatorNode *convert_call = alloc_node<OperatorNode>(); - convert_call->line = lv->line; - convert_call->op = OperatorNode::OP_CALL; - convert_call->arguments.push_back(convert); - convert_call->arguments.push_back(lv->assign); - convert_call->arguments.push_back(tgt_type); - - lv->assign = convert_call; - lv->assign_op->arguments.write[1] = convert_call; -#ifdef DEBUG_ENABLED - if (lv->datatype.builtin_type == Variant::INT && assign_type.builtin_type == Variant::FLOAT) { - _add_warning(GDScriptWarning::NARROWING_CONVERSION, lv->line); - } -#endif // DEBUG_ENABLED - } - } - if (lv->datatype.infer_type) { - if (!assign_type.has_type) { - _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line); - return; - } - 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]))); - } - -#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; - - // Try supertype test - if (_is_type_compatible(rh_type, lh_type)) { - _mark_line_as_unsafe(op->line); - } else { - // Try implicit conversion - if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) { - _set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" + - lh_type.to_string() + ").", - op->line); - return; - } - if (op->op == OperatorNode::OP_ASSIGN) { - // Replace assignment with implicit conversion - BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>(); - convert->line = op->line; - convert->function = GDScriptFunctions::TYPE_CONVERT; - - ConstantNode *tgt_type = alloc_node<ConstantNode>(); - tgt_type->line = op->line; - tgt_type->value = (int)lh_type.builtin_type; - tgt_type->datatype = _type_from_variant(tgt_type->value); - - OperatorNode *convert_call = alloc_node<OperatorNode>(); - convert_call->line = op->line; - convert_call->op = OperatorNode::OP_CALL; - convert_call->arguments.push_back(convert); - convert_call->arguments.push_back(op->arguments[1]); - convert_call->arguments.push_back(tgt_type); - - op->arguments.write[1] = convert_call; - - type_match = true; // Since we are converting, the type is matching - } -#ifdef DEBUG_ENABLED - if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::FLOAT) { - _add_warning(GDScriptWarning::NARROWING_CONVERSION, op->line); - } -#endif // DEBUG_ENABLED - } - } -#ifdef DEBUG_ENABLED - if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) { - _mark_line_as_unsafe(op->line); - } -#endif // DEBUG_ENABLED - op->datatype.has_type = type_match; - } break; - case OperatorNode::OP_CALL: - case OperatorNode::OP_PARENT_CALL: { - _mark_line_as_safe(op->line); - DataType func_type = _reduce_function_call_type(op); -#ifdef DEBUG_ENABLED - if (func_type.has_type && (func_type.kind != DataType::BUILTIN || func_type.builtin_type != Variant::NIL)) { - // Figure out function name for warning - String func_name = _find_function_name(op); - if (func_name.empty()) { - func_name = "<undetected name>"; - } - _add_warning(GDScriptWarning::RETURN_VALUE_DISCARDED, op->line, func_name); - } -#endif // DEBUG_ENABLED - if (error_set) return; - } break; - case OperatorNode::OP_YIELD: { - _mark_line_as_safe(op->line); - _reduce_node_type(op); - } break; - default: { - _mark_line_as_safe(op->line); - _reduce_node_type(op); // Test for safety anyway -#ifdef DEBUG_ENABLED - if (op->op == OperatorNode::OP_TERNARY_IF) { - _add_warning(GDScriptWarning::STANDALONE_TERNARY, statement->line); - } else { - _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); - } -#endif // DEBUG_ENABLED - } - } - } break; - case Node::TYPE_CONTROL_FLOW: { - ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement); - _mark_line_as_safe(cf->line); - - switch (cf->cf_type) { - case ControlFlowNode::CF_RETURN: { - DataType function_type = current_function->get_datatype(); - - DataType ret_type; - if (cf->arguments.size() > 0) { - ret_type = _reduce_node_type(cf->arguments[0]); - if (error_set) { - return; - } - } - - 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; - } +void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) { + push_text("Class "); + if (p_class->identifier == nullptr) { + push_text("<unnamed>"); + } else { + print_identifier(p_class->identifier); + } - if (!_is_type_compatible(function_type, ret_type)) { - _set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" + - function_type.to_string() + ").", - cf->line, cf->column); - return; - } - } - } break; - case ControlFlowNode::CF_MATCH: { - MatchNode *match_node = cf->match; - _transform_match_statment(match_node); - } break; - default: { - if (cf->body_else) { - _mark_line_as_safe(cf->body_else->line); - } - for (int i = 0; i < cf->arguments.size(); i++) { - _reduce_node_type(cf->arguments[i]); - } - } break; - } - } break; - case Node::TYPE_CONSTANT: { - ConstantNode *cn = static_cast<ConstantNode *>(statement); - // Strings are fine since they can be multiline comments - if (cn->value.get_type() == Variant::STRING) { - break; - } - [[fallthrough]]; - } - default: { - _mark_line_as_safe(statement->line); - _reduce_node_type(statement); // Test for safety anyway -#ifdef DEBUG_ENABLED - _add_warning(GDScriptWarning::STANDALONE_EXPRESSION, statement->line); -#endif // DEBUG_ENABLED + if (p_class->extends_used) { + bool first = true; + push_text(" Extends "); + if (!p_class->extends_path.is_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; } + push_text(p_class->extends[i]); } } - // Parse sub blocks - for (int i = 0; i < p_block->sub_blocks.size(); i++) { - current_block = p_block->sub_blocks[i]; - _check_block_types(current_block); - current_block = p_block; - if (error_set) return; - } + push_line(" :"); -#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); - } + increase_indent(); + + 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: + 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 -} -void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) { + decrease_indent(); +} - if (error_set) - return; //allow no further errors +void GDScriptParser::TreePrinter::print_constant(ConstantNode *p_constant) { + push_text("Constant "); + print_identifier(p_constant->identifier); - 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; -} + increase_indent(); -#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); + push_line(); + push_text("= "); + if (p_constant->initializer == nullptr) { + push_text("<missing value>"); + } else { + print_expression(p_constant->initializer); } - _add_warning(p_code, p_line, symbols); + decrease_indent(); + 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; +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("}"); +} - 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) { +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; - } - before = E; } - if (before) { - warnings.insert_after(before, warn); +} + +void GDScriptParser::TreePrinter::print_enum(EnumNode *p_enum) { + push_text("Enum "); + if (p_enum->identifier != nullptr) { + print_identifier(p_enum->identifier); } else { - warnings.push_front(warn); + push_text("<unnamed>"); } + + push_line(" {"); + increase_indent(); + for (int i = 0; i < p_enum->values.size(); i++) { + const EnumNode::Value &item = p_enum->values[i]; + print_identifier(item.identifier); + push_text(" = "); + push_text(itos(item.value)); + push_line(" ,"); + } + decrease_indent(); + push_line("}"); } -#endif // DEBUG_ENABLED -String GDScriptParser::get_error() const { +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(" :"); - return error; -} + increase_indent(); -int GDScriptParser::get_error_line() const { + print_suite(p_for->loop); - return error_line; + decrease_indent(); } -int GDScriptParser::get_error_column() const { - return error_column; +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()); + } + 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(); } -bool GDScriptParser::has_error() const { - return error_set; +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]); + } + } } -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); +void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) { + push_text(p_identifier->name); +} - if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) { - error_set = false; - _set_error("Parse error: " + tokenizer->get_token_error()); +void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { + if (p_is_elif) { + push_text("Elif "); + } else { + push_text("If "); } + print_expression(p_if->condition); + push_line(" :"); - bool for_completion_error_set = false; - if (error_set && for_completion) { - for_completion_error_set = true; - error_set = false; - } + increase_indent(); + print_suite(p_if->true_block); + decrease_indent(); - if (error_set) { - return ERR_PARSE_ERROR; + // FIXME: Properly detect "elif" blocks. + if (p_if->false_block != nullptr) { + push_line("Else :"); + increase_indent(); + print_suite(p_if->false_block); + decrease_indent(); } +} - if (dependencies_only) { - return OK; +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; } - - _determine_inheritance(main_class); - - if (error_set) { - return ERR_PARSE_ERROR; + 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; } +} - 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); +void GDScriptParser::TreePrinter::print_match(MatchNode *p_match) { + push_text("Match "); + print_expression(p_match->test); + push_line(" :"); - if (error_set) { - return ERR_PARSE_ERROR; + increase_indent(); + for (int i = 0; i < p_match->branches.size(); i++) { + print_match_branch(p_match->branches[i]); } + decrease_indent(); +} - // Resolve the function blocks - _check_class_blocks_types(main_class); - - if (for_completion_error_set) { - error_set = true; +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]); } - if (error_set) { - return ERR_PARSE_ERROR; - } + push_line(" :"); -#ifdef DEBUG_ENABLED + increase_indent(); + print_suite(p_match_branch->block); + decrease_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; +void GDScriptParser::TreePrinter::print_match_pattern(PatternNode *p_match_pattern) { + switch (p_match_pattern->pattern_type) { + case PatternNode::PT_LITERAL: + print_literal(p_match_pattern->literal); + break; + case PatternNode::PT_WILDCARD: + push_text("_"); + break; + case PatternNode::PT_REST: + push_text(".."); + break; + case PatternNode::PT_BIND: + push_text("Var "); + print_identifier(p_match_pattern->bind); + break; + case PatternNode::PT_EXPRESSION: + print_expression(p_match_pattern->expression); + break; + case PatternNode::PT_ARRAY: + push_text("[ "); + for (int i = 0; i < p_match_pattern->array.size(); i++) { + if (i > 0) { + push_text(" , "); + } + print_match_pattern(p_match_pattern->array[i]); } - 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_text(" ]"); + break; + case PatternNode::PT_DICTIONARY: + push_text("{ "); + for (int i = 0; i < p_match_pattern->dictionary.size(); i++) { + if (i > 0) { + push_text(" , "); + } + if (p_match_pattern->dictionary[i].key != nullptr) { + // Key can be null for rest pattern. + print_expression(p_match_pattern->dictionary[i].key); + push_text(" : "); + } + print_match_pattern(p_match_pattern->dictionary[i].value_pattern); } - 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; + push_text(" }"); + break; } -#endif // DEBUG_ENABLED - - return OK; } -Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const String &p_base_path, const String &p_self_path) { - - clear(); - - self_path = p_self_path; - GDScriptTokenizerBuffer *tb = memnew(GDScriptTokenizerBuffer); - tb->set_code_buffer(p_bytecode); - tokenizer = tb; - Error ret = _parse(p_base_path); - memdelete(tb); - tokenizer = nullptr; - return ret; +void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) { + print_identifier(p_parameter->identifier); + if (p_parameter->datatype_specifier != nullptr) { + push_text(" : "); + print_type(p_parameter->datatype_specifier); + } + if (p_parameter->default_value != nullptr) { + push_text(" = "); + print_expression(p_parameter->default_value); + } } -Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines, bool p_dependencies_only) { - - clear(); - - self_path = p_self_path; - GDScriptTokenizerText *tt = memnew(GDScriptTokenizerText); - tt->set_code(p_code); - - validating = p_just_validate; - for_completion = p_for_completion; - dependencies_only = p_dependencies_only; -#ifdef DEBUG_ENABLED - safe_lines = r_safe_lines; -#endif // DEBUG_ENABLED - tokenizer = tt; - Error ret = _parse(p_base_path); - memdelete(tt); - tokenizer = nullptr; - return ret; +void GDScriptParser::TreePrinter::print_preload(PreloadNode *p_preload) { + push_text(R"(Preload ( ")"); + push_text(p_preload->resolved_path); + push_text(R"(" )"); } -bool GDScriptParser::is_tool_script() const { - - return (head && head->type == Node::TYPE_CLASS && static_cast<const ClassNode *>(head)->tool); +void GDScriptParser::TreePrinter::print_return(ReturnNode *p_return) { + push_text("Return"); + if (p_return->return_value != nullptr) { + push_text(" "); + print_expression(p_return->return_value); + } + push_line(); } -const GDScriptParser::Node *GDScriptParser::get_parse_tree() const { - - return head; +void GDScriptParser::TreePrinter::print_self(SelfNode *p_self) { + push_text("Self("); + if (p_self->current_class->identifier != nullptr) { + print_identifier(p_self->current_class->identifier); + } else { + push_text("<main class>"); + } + push_text(")"); } -void GDScriptParser::clear() { - - while (list) { - - Node *l = list; - list = list->next; - memdelete(l); +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]); } - - 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 + push_line(" )"); } -GDScriptParser::CompletionType GDScriptParser::get_completion_type() { - - return completion_type; +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(" ]"); + } } -StringName GDScriptParser::get_completion_cursor() { - - return completion_cursor; +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; + } } -int GDScriptParser::get_completion_line() { - - return completion_line; +void GDScriptParser::TreePrinter::print_suite(SuiteNode *p_suite) { + for (int i = 0; i < p_suite->statements.size(); i++) { + print_statement(p_suite->statements[i]); + } } -Variant::Type GDScriptParser::get_completion_built_in_constant() { - - return completion_built_in_constant; +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(")"); } -GDScriptParser::Node *GDScriptParser::get_completion_node() { - - return completion_node; +void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) { + if (p_type->type_chain.is_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]); + } + } } -GDScriptParser::BlockNode *GDScriptParser::get_completion_block() { - - return completion_block; +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(")"); } -GDScriptParser::ClassNode *GDScriptParser::get_completion_class() { +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()); + } - return completion_class; -} + push_text("Variable "); + print_identifier(p_variable->identifier); -GDScriptParser::FunctionNode *GDScriptParser::get_completion_function() { + 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"); + } - return completion_function; -} + increase_indent(); -int GDScriptParser::get_completion_argument_index() { + 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(); + } + } + } - return completion_argument; + decrease_indent(); + push_line(); } -int GDScriptParser::get_completion_identifier_is_function() { +void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) { + push_text("While "); + print_expression(p_while->condition); + push_line(" :"); - return completion_ident_is_call; + increase_indent(); + print_suite(p_while->loop); + decrease_indent(); } -GDScriptParser::GDScriptParser() { - - head = nullptr; - list = nullptr; - tokenizer = nullptr; - pending_newline = -1; - clear(); -} +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."); -GDScriptParser::~GDScriptParser() { + if (p_parser.is_tool()) { + push_line("@tool"); + } + if (!p_parser.get_tree()->icon_path.is_empty()) { + push_text(R"(@icon (")"); + push_text(p_parser.get_tree()->icon_path); + push_line("\")"); + } + print_class(p_parser.get_tree()); - clear(); + print_line(printed); } + +#endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index f254352423..f43708b81f 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,665 +31,1359 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H -#include "core/map.h" -#include "core/object.h" -#include "core/script_language.h" -#include "gdscript_functions.h" +#include "core/io/multiplayer_api.h" +#include "core/io/resource.h" +#include "core/object/reference.h" +#include "core/object/script_language.h" +#include "core/string/string_name.h" +#include "core/string/ustring.h" +#include "core/templates/hash_map.h" +#include "core/templates/list.h" +#include "core/templates/map.h" +#include "core/templates/vector.h" +#include "core/variant/variant.h" +#include "gdscript_cache.h" #include "gdscript_tokenizer.h" -struct GDScriptDataType; -struct GDScriptWarning; +#ifdef DEBUG_ENABLED +#include "core/string/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; - 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 + 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; - Variant::Type builtin_type; + bool is_constant = false; + bool is_meta_type = false; + bool is_coroutine = false; // For function calls. + + Variant::Type builtin_type = Variant::NIL; StringName native_type; + StringName enum_type; // Enum name or the value name in an enum. Ref<Script> script_type; - 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 (kind != other.kind) { + + if (type_source == INFERRED || p_other.type_source == INFERRED) { + return true; // Can be consireded equal for parsing purposes. + } + + 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; + + DataType datatype; - virtual DataType get_datatype() const { return DataType(); } - virtual void set_datatype(const DataType &p_datatype) {} + virtual DataType get_datatype() const { return datatype; } + virtual void set_datatype(const DataType &p_datatype) { datatype = p_datatype; } + + virtual bool is_expression() const { return false; } virtual ~Node() {} }; - struct FunctionNode; - struct BlockNode; - struct ConstantNode; - struct LocalVarNode; - struct OperatorNode; + struct ExpressionNode : public Node { + // Base type for all expression kinds. + bool reduced = false; + bool is_constant = false; + Variant reduced_value; - struct ClassNode : public Node { + virtual bool is_expression() const { return true; } + virtual ~ExpressionNode() {} - bool tool; + protected: + ExpressionNode() {} + }; + + struct AnnotationNode : public Node { StringName name; - bool extends_used; - bool classname_used; - StringName extends_file; - Vector<StringName> extends_class; - DataType base_type; - String icon_path; + Vector<ExpressionNode *> arguments; + Vector<Variant> resolved_arguments; - struct Member { - PropertyInfo _export; -#ifdef TOOLS_ENABLED - Variant default_value; -#endif - StringName identifier; - DataType data_type; - StringName setter; - StringName getter; - int line; - Node *expression; - OperatorNode *initial_assignment; - MultiplayerAPI::RPCMode rpc_mode; - int usages; - }; + AnnotationInfo *info = nullptr; - struct Constant { - Node *expression; - DataType type; - }; + bool apply(GDScriptParser *p_this, Node *p_target) const; + bool applies_to(uint32_t p_target_kinds) const; - struct Signal { - StringName name; - Vector<StringName> arguments; - int emissions; - int line; - }; + AnnotationNode() { + type = ANNOTATION; + } + }; - Vector<ClassNode *> subclasses; - Vector<Member> variables; - Map<StringName, Constant> constant_expressions; - Vector<FunctionNode *> functions; - Vector<FunctionNode *> static_functions; - Vector<Signal> _signals; - BlockNode *initializer; - BlockNode *ready; - ClassNode *owner; - //Vector<Node*> initializers; - int end_line; + struct ArrayNode : public ExpressionNode { + Vector<ExpressionNode *> elements; - ClassNode() { - tool = false; - type = TYPE_CLASS; - extends_used = false; - classname_used = false; - end_line = -1; - owner = nullptr; + ArrayNode() { + type = ARRAY; } }; - struct FunctionNode : public Node { + struct AssertNode : public Node { + ExpressionNode *condition = nullptr; + ExpressionNode *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, + }; + + OpType operation; + Variant::Operator variant_op = Variant::OP_MAX; + ExpressionNode *left_operand = nullptr; + ExpressionNode *right_operand = nullptr; - //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; + BinaryOpNode() { + type = BINARY_OPERATOR; } }; - struct TypeNode : public Node { + struct BreakNode : public Node { + BreakNode() { + type = BREAK; + } + }; - Variant::Type vtype; - TypeNode() { type = TYPE_TYPE; } + struct BreakpointNode : public Node { + BreakpointNode() { + type = BREAKPOINT; + } }; - struct BuiltInFunctionNode : public Node { - GDScriptFunctions::Function function; - BuiltInFunctionNode() { type = TYPE_BUILT_IN_FUNCTION; } + + struct CallNode : public ExpressionNode { + ExpressionNode *callee = nullptr; + Vector<ExpressionNode *> arguments; + StringName function_name; + bool is_super = false; + + CallNode() { + type = CALL; + } + + Type get_callee_type() const { + if (callee == nullptr) { + return Type::NONE; + } else { + return callee->type; + } + } }; - struct IdentifierNode : public Node { + struct CastNode : public ExpressionNode { + ExpressionNode *operand = nullptr; + TypeNode *cast_type = nullptr; - 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; + CastNode() { + type = CAST; } }; - struct LocalVarNode : public Node { + struct EnumNode : public Node { + struct Value { + IdentifierNode *identifier = nullptr; + ExpressionNode *custom_value = nullptr; + EnumNode *parent_enum = nullptr; + int index = -1; + bool resolved = false; + int value = 0; + int line = 0; + int leftmost_column = 0; + int rightmost_column = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED + }; - 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; + IdentifierNode *identifier = nullptr; + Vector<Value> values; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED + + EnumNode() { + type = ENUM; } }; - 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 ClassNode : public Node { + struct Member { + enum Type { + UNDEFINED, + CLASS, + CONSTANT, + FUNCTION, + SIGNAL, + VARIABLE, + ENUM, + ENUM_VALUE, // For unnamed enums. + }; + + Type type = UNDEFINED; + + union { + ClassNode *m_class = nullptr; + ConstantNode *constant; + FunctionNode *function; + SignalNode *signal; + VariableNode *variable; + EnumNode *m_enum; + }; + EnumNode::Value enum_value; + + String get_type_name() const { + switch (type) { + case UNDEFINED: + return "???"; + case CLASS: + return "class"; + case CONSTANT: + return "constant"; + case FUNCTION: + return "function"; + case SIGNAL: + return "signal"; + case VARIABLE: + return "variable"; + case ENUM: + return "enum"; + case ENUM_VALUE: + return "enum value"; + } + return ""; + } + + int get_line() const { + switch (type) { + case CLASS: + return m_class->start_line; + case CONSTANT: + return constant->start_line; + case FUNCTION: + return function->start_line; + case VARIABLE: + return variable->start_line; + case ENUM_VALUE: + return enum_value.line; + case ENUM: + return m_enum->start_line; + case SIGNAL: + return signal->start_line; + case UNDEFINED: + ERR_FAIL_V_MSG(-1, "Reaching undefined member type."); + } + ERR_FAIL_V_MSG(-1, "Reaching unhandled type."); + } + + DataType get_datatype() const { + switch (type) { + case CLASS: + return m_class->get_datatype(); + case CONSTANT: + return constant->get_datatype(); + case FUNCTION: + return function->get_datatype(); + case VARIABLE: + return variable->get_datatype(); + case ENUM: + return m_enum->get_datatype(); + case ENUM_VALUE: { + // Always integer. + DataType type; + type.type_source = DataType::ANNOTATED_EXPLICIT; + type.kind = DataType::BUILTIN; + type.builtin_type = Variant::INT; + return type; + } + case SIGNAL: { + DataType type; + type.type_source = DataType::ANNOTATED_EXPLICIT; + type.kind = DataType::BUILTIN; + type.builtin_type = Variant::SIGNAL; + // TODO: Add parameter info. + return type; + } + case UNDEFINED: + return DataType(); + } + ERR_FAIL_V_MSG(DataType(), "Reaching unhandled type."); + } + + Member() {} + + Member(ClassNode *p_class) { + type = CLASS; + m_class = p_class; + } + Member(ConstantNode *p_constant) { + type = CONSTANT; + constant = p_constant; + } + Member(VariableNode *p_variable) { + type = VARIABLE; + variable = p_variable; + } + Member(SignalNode *p_signal) { + type = SIGNAL; + signal = p_signal; + } + Member(FunctionNode *p_function) { + type = FUNCTION; + function = p_function; + } + Member(EnumNode *p_enum) { + type = ENUM; + m_enum = p_enum; + } + Member(const EnumNode::Value &p_enum_value) { + type = ENUM_VALUE; + enum_value = p_enum_value; + } + }; + + IdentifierNode *identifier = nullptr; + String icon_path; + Vector<Member> members; + HashMap<StringName, int> members_indices; + ClassNode *outer = nullptr; + bool extends_used = false; + bool onready_used = false; + String extends_path; + Vector<StringName> extends; // List for indexing: extends A.B.C + DataType base_type; + String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project. +#ifdef TOOLS_ENABLED + String doc_description; + String doc_brief_description; + Vector<Pair<String, String>> doc_tutorials; + + // EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later. + void set_enum_value_doc(const StringName &p_name, const String &p_doc_description) { + ERR_FAIL_INDEX(members_indices[p_name], members.size()); + members.write[members_indices[p_name]].enum_value.doc_description = p_doc_description; + } +#endif // TOOLS_ENABLED + + bool resolved_interface = false; + bool resolved_body = false; + + 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 ArrayNode : public Node { + struct ConstantNode : public Node { + IdentifierNode *identifier = nullptr; + ExpressionNode *initializer = nullptr; + TypeNode *datatype_specifier = nullptr; + bool infer_datatype = false; + int usages = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED - 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; + ConstantNode() { + type = CONSTANT; } }; - struct DictionaryNode : public Node { + struct ContinueNode : public Node { + bool is_for_match = false; + 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; +#ifdef TOOLS_ENABLED + Vector<Variant> default_arg_values; + String doc_description; +#endif // TOOLS_ENABLED - enum PatternType { - PT_CONSTANT, - PT_BIND, - PT_DICTIONARY, - PT_ARRAY, - PT_IGNORE_REST, - PT_WILDCARD + bool resolved_signature = false; + bool resolved_body = false; + + 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; + } + }; + + struct MatchBranchNode : public Node { + Vector<PatternNode *> patterns; + SuiteNode *block; + bool has_wildcard = false; + + MatchBranchNode() { + type = MATCH_BRANCH; + } + }; + + struct ParameterNode : public Node { + IdentifierNode *identifier = nullptr; + ExpressionNode *default_value = nullptr; + TypeNode *datatype_specifier = nullptr; + bool infer_datatype = false; + int usages = 0; + + ParameterNode() { + type = PARAMETER; + } + }; - Vector<CompiledPatternBranch> compiled_pattern_branches; + struct PassNode : public Node { + PassNode() { + type = PASS; + } }; - struct ControlFlowNode : public Node { - enum CFType { - CF_IF, - CF_FOR, - CF_WHILE, - CF_BREAK, - CF_CONTINUE, - CF_RETURN, - CF_MATCH + struct PatternNode : public Node { + enum Type { + PT_LITERAL, + PT_EXPRESSION, + PT_BIND, + PT_ARRAY, + PT_DICTIONARY, + PT_REST, + PT_WILDCARD, + }; + Type pattern_type = PT_LITERAL; + + union { + LiteralNode *literal = nullptr; + IdentifierNode *bind; + ExpressionNode *expression; + }; + Vector<PatternNode *> array; + bool rest_used = false; // For array/dict patterns. + + struct Pair { + ExpressionNode *key = nullptr; + PatternNode *value_pattern = nullptr; }; + Vector<Pair> dictionary; - CFType cf_type; - Vector<Node *> arguments; - BlockNode *body; - BlockNode *body_else; + HashMap<StringName, IdentifierNode *> binds; - MatchNode *match; + bool has_bind(const StringName &p_name); + IdentifierNode *get_bind(const StringName &p_name); - ControlFlowNode *_else; //used for if - ControlFlowNode() { - type = TYPE_CONTROL_FLOW; - cf_type = CF_IF; - body = nullptr; - body_else = nullptr; + 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; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED + + SignalNode() { + type = SIGNAL; + } + }; - bool is_op; + struct SubscriptNode : public ExpressionNode { + ExpressionNode *base = nullptr; union { - OperatorNode::Operator op; - Node *node; + ExpressionNode *index = nullptr; + IdentifierNode *attribute; }; - }; - enum CompletionType { - COMPLETION_NONE, - COMPLETION_BUILT_IN_TYPE_CONSTANT, - COMPLETION_GET_NODE, - COMPLETION_FUNCTION, - COMPLETION_IDENTIFIER, - COMPLETION_PARENT_FUNCTION, - COMPLETION_METHOD, - COMPLETION_CALL_ARGUMENTS, - COMPLETION_RESOURCE_PATH, - COMPLETION_INDEX, - COMPLETION_VIRTUAL_FUNC, - COMPLETION_YIELD, - COMPLETION_ASSIGN, - COMPLETION_TYPE_HINT, - COMPLETION_TYPE_HINT_INDEX, + bool is_attribute = false; + + SubscriptNode() { + type = SUBSCRIPT; + } }; -private: - GDScriptTokenizer *tokenizer; + struct SuiteNode : public Node { + SuiteNode *parent_block = nullptr; + Vector<Node *> statements; + struct Local { + enum Type { + UNDEFINED, + CONSTANT, + VARIABLE, + PARAMETER, + FOR_VARIABLE, + PATTERN_BIND, + }; + Type type = UNDEFINED; + union { + ConstantNode *constant = nullptr; + VariableNode *variable; + ParameterNode *parameter; + IdentifierNode *bind; + }; + StringName name; - Node *head; - Node *list; - template <class T> - T *alloc_node(); - - bool validating; - bool for_completion; - int parenthesis; - bool error_set; - String error; - int error_line; - int error_column; - bool check_types; - bool dependencies_only; - List<String> dependencies; -#ifdef DEBUG_ENABLED - Set<int> *safe_lines; -#endif // DEBUG_ENABLED + int start_line = 0, end_line = 0; + int start_column = 0, end_column = 0; + int leftmost_column = 0, rightmost_column = 0; + + DataType get_datatype() const; + String get_name() const; + + Local() {} + Local(ConstantNode *p_constant) { + type = CONSTANT; + constant = p_constant; + name = p_constant->identifier->name; + + start_line = p_constant->start_line; + end_line = p_constant->end_line; + start_column = p_constant->start_column; + end_column = p_constant->end_column; + leftmost_column = p_constant->leftmost_column; + rightmost_column = p_constant->rightmost_column; + } + Local(VariableNode *p_variable) { + type = VARIABLE; + variable = p_variable; + name = p_variable->identifier->name; + + start_line = p_variable->start_line; + end_line = p_variable->end_line; + start_column = p_variable->start_column; + end_column = p_variable->end_column; + leftmost_column = p_variable->leftmost_column; + rightmost_column = p_variable->rightmost_column; + } + Local(ParameterNode *p_parameter) { + type = PARAMETER; + parameter = p_parameter; + name = p_parameter->identifier->name; + + start_line = p_parameter->start_line; + end_line = p_parameter->end_line; + start_column = p_parameter->start_column; + end_column = p_parameter->end_column; + leftmost_column = p_parameter->leftmost_column; + rightmost_column = p_parameter->rightmost_column; + } + Local(IdentifierNode *p_identifier) { + type = FOR_VARIABLE; + bind = p_identifier; + name = p_identifier->name; + + start_line = p_identifier->start_line; + end_line = p_identifier->end_line; + start_column = p_identifier->start_column; + end_column = p_identifier->end_column; + leftmost_column = p_identifier->leftmost_column; + rightmost_column = p_identifier->rightmost_column; + } + }; + Local empty; + Vector<Local> locals; + HashMap<StringName, int> locals_indices; + + 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; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED - 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); - bool _reduce_export_var_type(Variant &p_value, int p_line = 0); - - PatternNode *_parse_pattern(bool p_static); - void _parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static); - void _transform_match_statment(MatchNode *p_match_statement); - void _generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings); - - void _parse_block(BlockNode *p_block, bool p_static); - void _parse_extends(ClassNode *p_class); - void _parse_class(ClassNode *p_class); - bool _end_statement(); - - void _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_continue_match = false; // Whether a `continue` will act on a `match`. + 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_ADDITION_SUBTRACTION, + 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); +#ifdef TOOLS_ENABLED + // Doc comments. + int class_doc_line = 0x7FFFFFFF; + bool has_comment(int p_line); + String get_doc_comment(int p_line, bool p_single_line = false); + void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class); +#endif // TOOLS_ENABLED + +public: + Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion); + ClassNode *get_tree() const { return head; } + bool is_tool() const { return _is_tool; } + static Variant::Type get_builtin_type(const StringName &p_type); + + 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..315b8ee3b4 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -30,1503 +30,1400 @@ #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/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: +bool GDScriptTokenizer::Token::is_identifier() const { + // Note: Most keywords should not be recognized as identifiers. + // These are only exceptions for stuff that already is on the engine's API. + switch (type) { + case IDENTIFIER: + case MATCH: // Used in String.match(). return true; - - // 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; } } -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++; - } - } +bool GDScriptTokenizer::Token::is_node_name() const { + // This is meant to allow keywords with the $ notation, but not as general identifiers. + switch (type) { + case IDENTIFIER: + case AND: + case AS: + case ASSERT: + case AWAIT: + case BREAK: + case BREAKPOINT: + case CLASS_NAME: + case CLASS: + case CONST: + case CONTINUE: + case ELIF: + case ELSE: + case ENUM: + case EXTENDS: + case FOR: + case FUNC: + case IF: + case IN: + case IS: + case MATCH: + case NAMESPACE: + case NOT: + case OR: + case PASS: + case PRELOAD: + case RETURN: + case SELF: + case SIGNAL: + case STATIC: + case SUPER: + case TRAIT: + case UNDERSCORE: + case VAR: + case VOID: + case WHILE: + case YIELD: + return true; + default: + return false; } - ERR_FAIL_V_MSG("", "Failed to get token literal."); } -static bool _is_text_char(CharType c) { - - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +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]; } -static bool _is_number(CharType c) { - - return (c >= '0' && c <= '9'); +void GDScriptTokenizer::set_source_code(const String &p_source_code) { + source = p_source_code; + if (source.is_empty()) { + _source = U""; + } else { + _source = source.ptr(); + } + _current = _source; + line = 1; + column = 1; + length = p_source_code.length(); + position = 0; } -static bool _is_hex(CharType c) { - - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +void GDScriptTokenizer::set_cursor_position(int p_line, int p_column) { + cursor_line = p_line; + cursor_column = p_column; } -static bool _is_bin(CharType c) { - - return (c == '0' || c == '1'); +void GDScriptTokenizer::set_multiline_mode(bool p_state) { + multiline_mode = p_state; } -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; +int GDScriptTokenizer::get_cursor_line() const { + return cursor_line; } -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; +int GDScriptTokenizer::get_cursor_column() const { + return cursor_column; } -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; +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_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; - - tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; +char32_t 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_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; +void GDScriptTokenizer::push_paren(char32_t p_char) { + paren_stack.push_back(p_char); } -void GDScriptTokenizerText::_make_error(const String &p_error) { - - error_flag = true; - last_error = p_error; +bool GDScriptTokenizer::pop_paren(char32_t p_expected) { + if (paren_stack.is_empty()) { + return false; + } + char32_t actual = paren_stack.back()->get(); + paren_stack.pop_back(); - 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; + return actual == p_expected; } -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; +GDScriptTokenizer::Token GDScriptTokenizer::pop_error() { + Token error = error_stack.back()->get(); + error_stack.pop_back(); + return error; } -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); - } +static bool _is_alphanumeric(char32_t c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +} - } 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_digit(char32_t c) { + return (c >= '0' && c <= '9'); +} - } 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_hex_digit(char32_t c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_MUL); - INCPOS(1); - } else { - _make_token(TK_OP_MUL); - } - } break; - case '+': { - - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_ADD); - INCPOS(1); - /* - } else if (GETCHAR(1)=='+') { - _make_token(TK_OP_PLUS_PLUS); - INCPOS(1); - */ - } else { - _make_token(TK_OP_ADD); - } +static bool _is_binary_digit(char32_t c) { + return (c == '0' || c == '1'); +} - } break; - case '-': { - - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_SUB); - INCPOS(1); - } else if (GETCHAR(1) == '>') { - _make_token(TK_FORWARD_ARROW); - INCPOS(1); +GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) { + Token token(p_type); + token.start_line = start_line; + token.end_line = line; + token.start_column = start_column; + token.end_column = column; + token.leftmost_column = leftmost_column; + token.rightmost_column = rightmost_column; + token.source = String(_start, _current - _start); + + if (p_type != Token::ERROR && cursor_line > -1) { + // Also count whitespace after token. + int offset = 0; + while (_peek(offset) == ' ' || _peek(offset) == '\t') { + offset++; + } + int last_column = column + offset; + // Check cursor position in token. + if (start_line == line) { + // Single line token. + if (cursor_line == start_line && cursor_column >= start_column && cursor_column <= last_column) { + token.cursor_position = cursor_column - start_column; + if (cursor_column == start_column) { + token.cursor_place = CURSOR_BEGINNING; + } else if (cursor_column < column) { + token.cursor_place = CURSOR_MIDDLE; } else { - _make_token(TK_OP_SUB); + token.cursor_place = CURSOR_END; } - } break; - case '%': { - - if (GETCHAR(1) == '=') { - _make_token(TK_OP_ASSIGN_MOD); - INCPOS(1); + } + } else { + // Multi line token. + if (cursor_line == start_line && cursor_column >= start_column) { + // Is in first line. + token.cursor_position = cursor_column - start_column; + if (cursor_column == start_column) { + token.cursor_place = CURSOR_BEGINNING; } else { - _make_token(TK_OP_MOD); - } - } break; - case '@': - if (CharType(GETCHAR(1)) != '"' && CharType(GETCHAR(1)) != '\'') { - _make_error("Unexpected '@'"); - return; + token.cursor_place = CURSOR_MIDDLE; } - 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; - } - - 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 if (cursor_line == line && cursor_column <= last_column) { + // Is in last line. + token.cursor_position = cursor_column - start_column; + if (cursor_column < column) { + token.cursor_place = CURSOR_MIDDLE; } else { - _make_constant(str); - } - - } break; - case 0xFFFF: { - _make_token(TK_CURSOR); - } break; - default: { - - if (_is_number(GETCHAR(0)) || (GETCHAR(0) == '.' && _is_number(GETCHAR(1)))) { - // parse number - bool period_found = false; - bool exponent_found = false; - bool hexa_found = false; - bool bin_found = false; - bool sign_found = false; - - String str; - int i = 0; - - while (true) { - if (GETCHAR(i) == '.') { - if (period_found || exponent_found) { - _make_error("Invalid numeric constant at '.'"); - return; - } else if (bin_found) { - _make_error("Invalid binary constant at '.'"); - return; - } else if (hexa_found) { - _make_error("Invalid hexadecimal constant at '.'"); - return; - } - period_found = true; - } else if (GETCHAR(i) == 'x') { - if (hexa_found || bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) { - _make_error("Invalid numeric constant at 'x'"); - return; - } - hexa_found = true; - } else if (hexa_found && _is_hex(GETCHAR(i))) { - - } else if (!hexa_found && GETCHAR(i) == 'b') { - if (bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) { - _make_error("Invalid numeric constant at 'b'"); - return; - } - bin_found = true; - } else if (!hexa_found && GETCHAR(i) == 'e') { - if (exponent_found || bin_found) { - _make_error("Invalid numeric constant at 'e'"); - return; - } - exponent_found = true; - } else if (_is_number(GETCHAR(i))) { - //all ok - - } else if (bin_found && _is_bin(GETCHAR(i))) { - - } else if ((GETCHAR(i) == '-' || GETCHAR(i) == '+') && exponent_found) { - if (sign_found) { - _make_error("Invalid numeric constant at '-'"); - return; - } - sign_found = true; - } else if (GETCHAR(i) == '_') { - i++; - continue; // Included for readability, shouldn't be a part of the string - } else - break; - - str += CharType(GETCHAR(i)); - i++; - } - - if (!(_is_number(str[str.length() - 1]) || (hexa_found && _is_hex(str[str.length() - 1])))) { - _make_error("Invalid numeric constant: " + str); - return; - } - - INCPOS(i); - if (hexa_found) { - int64_t val = str.hex_to_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; + token.cursor_place = CURSOR_END; } - - 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(char32_t p_paren) { + if (paren_stack.is_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(char32_t p_test, Token::Type p_double_type) { + const char32_t *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 continuation. + 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)(char32_t) = _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. + bool previous_was_underscore = false; + while (digit_check_func(_peek()) || _peek() == '_') { + if (_peek() == '_') { + if (previous_was_underscore) { + Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = true; + } + _advance(); } - 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. + if (!_is_digit(_peek())) { + Token error = make_error(R"(Expected exponent value after "e".)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = false; + while (_is_digit(_peek()) || _peek() == '_') { + if (_peek() == '_') { + if (previous_was_underscore) { + Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = true; + } + _advance(); + } } } - 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; + char32_t 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()) { + char32_t 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: { + if (ch == '\\') { + // Escape pattern. + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } - 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: { + // Grab escape character. + char32_t code = _peek(); + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } - token |= tt.get_token_type() << TOKEN_BITS; - } break; - case TK_BUILT_IN_FUNC: { + char32_t escaped = 0; + bool valid_escape = true; - token |= tt.get_token_built_in_func() << TOKEN_BITS; + 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."); + } - } break; - case TK_NEWLINE: { + char32_t digit = _peek(); + char32_t 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; + } - token |= tt.get_token_line_indent() << TOKEN_BITS; - } break; - case TK_ERROR: { + 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 (;;) { + char32_t 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()) { + char32_t 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. +#ifdef TOOLS_ENABLED + String comment; + while (_peek() != '\n' && !_is_at_end()) { + comment += _advance(); + } + comments[line] = CommentData(comment, true); +#else + while (_peek() != '\n' && !_is_at_end()) { + _advance(); + } +#endif // TOOLS_ENABLED + if (_is_at_end()) { + // Reached the end with an empty line, so just dedent as much as needed. + pending_indents -= indent_level(); + 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 \"%s\" for indentation instead \"%s\" 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 (;;) { + char32_t 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. +#ifdef TOOLS_ENABLED + String comment; + while (_peek() != '\n' && !_is_at_end()) { + comment += _advance(); + } + comments[line] = CommentData(comment, is_bol); +#else + while (_peek() != '\n' && !_is_at_end()) { + _advance(); + } +#endif // TOOLS_ENABLED + if (_is_at_end()) { + return; + } + _advance(); // Consume '\n' + newline(!is_bol); + check_indent(); + } break; + default: + return; + } + } } -Variant::Type GDScriptTokenizerBuffer::get_token_type(int p_offset) const { +GDScriptTokenizer::Token GDScriptTokenizer::scan() { + if (has_error()) { + return pop_error(); + } - int offset = token + p_offset; - ERR_FAIL_INDEX_V(offset, tokens.size(), Variant::NIL); + _skip_whitespace(); - return Variant::Type(tokens[offset] >> TOKEN_BITS); -} + if (pending_newline) { + pending_newline = false; + if (!multiline_mode) { + // Don't return newline tokens on multine mode. + return last_newline; + } + } -int GDScriptTokenizerBuffer::get_token_line(int p_offset) const { + // Check for potential errors after skipping whitespace(). + if (has_error()) { + return pop_error(); + } - int offset = token + p_offset; - int pos = lines.find_nearest(offset); + _start = _current; + start_line = line; + start_column = column; + leftmost_column = column; + rightmost_column = column; + + if (pending_indents != 0) { + // Adjust position for indent. + _start -= start_column - 1; + start_column = 1; + leftmost_column = 1; + if (pending_indents > 0) { + // Indents. + pending_indents--; + return make_token(Token::INDENT); + } else { + // Dedents. + pending_indents++; + Token dedent = make_token(Token::DEDENT); + dedent.end_column += 1; + dedent.rightmost_column += 1; + return dedent; + } + } - if (pos < 0) - return -1; - if (pos >= lines.size()) - pos = lines.size() - 1; + if (_is_at_end()) { + return make_token(Token::TK_EOF); + } - uint32_t l = lines.getv(pos); - return l & TOKEN_LINE_MASK; -} -int GDScriptTokenizerBuffer::get_token_column(int p_offset) const { + const char32_t c = _advance(); - int offset = token + p_offset; - int pos = lines.find_nearest(offset); - if (pos < 0) - return -1; - if (pos >= lines.size()) - pos = lines.size() - 1; + if (c == '\\') { + // Line continuation with backslash. + if (_peek() == '\r') { + if (_peek(1) != '\n') { + return make_error("Unexpected carriage return character."); + } + _advance(); + } + if (_peek() != '\n') { + return make_error("Expected new line after \"\\\"."); + } + _advance(); + newline(false); + line_continuation = true; + return scan(); // Recurse to get next token. + } - uint32_t l = lines.getv(pos); - return l >> TOKEN_LINE_BITS; -} -int GDScriptTokenizerBuffer::get_token_line_indent(int p_offset) const { + line_continuation = false; - 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 { - - 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 180ec3c77e..cdb0072294 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,270 +31,241 @@ #ifndef GDSCRIPT_TOKENIZER_H #define GDSCRIPT_TOKENIZER_H -#include "core/pair.h" -#include "core/set.h" -#include "core/string_name.h" -#include "core/ustring.h" -#include "core/variant.h" -#include "core/vmap.h" -#include "gdscript_functions.h" +#include "core/templates/list.h" +#include "core/templates/map.h" +#include "core/templates/set.h" +#include "core/templates/vector.h" +#include "core/variant/variant.h" class GDScriptTokenizer { public: - 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 + 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 + }; - virtual ~GDScriptTokenizer(){}; -}; + 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; -class GDScriptTokenizerText : public GDScriptTokenizer { + const char *get_name() const; + bool is_identifier() const; + bool is_node_name() const; + StringName get_identifier() const { return source; } - enum { - MAX_LOOKAHEAD = 4, - TK_RB_SIZE = MAX_LOOKAHEAD * 2 + 1 + Token(Type p_type) { + type = p_type; + } + Token() { + type = EMPTY; + } }; - 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; +#ifdef TOOLS_ENABLED + struct CommentData { + String comment; + bool new_line = false; + CommentData() {} + CommentData(const String &p_comment, bool p_new_line) { + comment = p_comment; + new_line = p_new_line; } }; + const Map<int, CommentData> &get_comments() const { + return comments; + } +#endif // TOOLS_ENABLED - 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); +private: + String source; + const char32_t *_source = nullptr; + const char32_t *_current = nullptr; + int line = -1, column = -1; + int cursor_line = -1, cursor_column = -1; + int tab_size = 4; - 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; + // Keep track of multichar tokens. + const char32_t *_start = nullptr; + int start_line = 0, start_column = 0; + int leftmost_column = 0, rightmost_column = 0; -#ifdef DEBUG_ENABLED - Vector<Pair<int, String>> warning_skips; - Set<String> warning_global_skips; - bool ignore_warnings; -#endif // DEBUG_ENABLED + // 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<char32_t> paren_stack; + char32_t indent_char = '\0'; + int position = 0; + int length = 0; - void _advance(); +#ifdef TOOLS_ENABLED + Map<int, CommentData> comments; +#endif // TOOLS_ENABLED -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 -}; + _FORCE_INLINE_ bool _is_at_end() { return position >= length; } + _FORCE_INLINE_ char32_t _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } + int indent_level() const { return indent_stack.size(); } + bool has_error() const { return !error_stack.is_empty(); } + Token pop_error(); + char32_t _advance(); + void _skip_whitespace(); + void check_indent(); -class GDScriptTokenizerBuffer : public GDScriptTokenizer { + 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(char32_t p_paren); + Token make_token(Token::Type p_type); + Token make_literal(const Variant &p_literal); + Token make_identifier(const StringName &p_identifier); + Token check_vcs_marker(char32_t p_test, Token::Type p_double_type); + void push_paren(char32_t p_char); + bool pop_paren(char32_t p_expected); - enum { + void newline(bool p_make_token); + Token number(); + Token potential_identifier(); + Token string(); + Token annotation(); - 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, - }; +public: + Token scan(); - Vector<StringName> identifiers; - Vector<Variant> constants; - VMap<uint32_t, uint32_t> lines; - Vector<uint32_t> tokens; - Variant nil; - int token; + void set_source_code(const String &p_source_code); -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(); + 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); + + GDScriptTokenizer(); }; -#endif // GDSCRIPT_TOKENIZER_H +#endif diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp new file mode 100644 index 0000000000..348d221352 --- /dev/null +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -0,0 +1,718 @@ +/*************************************************************************/ +/* gdscript_utility_functions.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_utility_functions.h" + +#include "core/io/resource_loader.h" +#include "core/object/class_db.h" +#include "core/object/method_bind.h" +#include "core/object/object.h" +#include "core/templates/oa_hash_map.h" +#include "core/templates/vector.h" +#include "gdscript.h" + +#ifdef DEBUG_ENABLED + +#define VALIDATE_ARG_COUNT(m_count) \ + 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; \ + } + +#define VALIDATE_ARG_INT(m_arg) \ + if (p_args[m_arg]->get_type() != Variant::INT) { \ + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ + r_error.argument = m_arg; \ + r_error.expected = Variant::INT; \ + *r_ret = Variant(); \ + return; \ + } + +#define VALIDATE_ARG_NUM(m_arg) \ + if (!p_args[m_arg]->is_num()) { \ + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \ + r_error.argument = m_arg; \ + r_error.expected = Variant::FLOAT; \ + *r_ret = Variant(); \ + return; \ + } + +#else + +#define VALIDATE_ARG_COUNT(m_count) +#define VALIDATE_ARG_INT(m_arg) +#define VALIDATE_ARG_NUM(m_arg) + +#endif + +struct GDScriptUtilityFunctionsDefinitions { + static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(2); + VALIDATE_ARG_INT(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; + r_error.expected = Variant::INT; + return; + + } else { + Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error); + } + } + + static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + *r_ret = ClassDB::class_exists(*p_args[0]); + } + + static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + VALIDATE_ARG_INT(0); + char32_t result[2] = { *p_args[0], 0 }; + *r_ret = String(result); + } + + static inline void str(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + if (p_arg_count < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 1; + *r_ret = Variant(); + return; + } + + String str; + for (int i = 0; i < p_arg_count; i++) { + String os = p_args[i]->operator String(); + + if (i == 0) { + str = os; + } else { + str += os; + } + } + *r_ret = str; + } + + static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + 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; + if (count <= 0) { + *r_ret = arr; + return; + } + Error err = arr.resize(count); + if (err != OK) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + *r_ret = Variant(); + return; + } + + for (int i = 0; i < count; i++) { + arr[i] = i; + } + + *r_ret = arr; + } break; + case 2: { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + + int from = *p_args[0]; + int to = *p_args[1]; + + Array arr; + if (from >= to) { + *r_ret = arr; + return; + } + Error err = arr.resize(to - from); + if (err != OK) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + *r_ret = Variant(); + return; + } + 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); + + int from = *p_args[0]; + 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; + } + + Array arr; + if (from >= to && incr > 0) { + *r_ret = arr; + return; + } + if (from <= to && incr < 0) { + *r_ret = arr; + return; + } + + // Calculate how many. + int count = 0; + if (incr > 0) { + count = ((to - from - 1) / incr) + 1; + } else { + count = ((from - to - 1) / -incr) + 1; + } + + Error err = arr.resize(count); + + if (err != OK) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + *r_ret = Variant(); + return; + } + + if (incr > 0) { + int idx = 0; + for (int i = from; i < to; i += incr) { + arr[idx++] = i; + } + } else { + int idx = 0; + for (int i = from; i > to; i += incr) { + arr[idx++] = i; + } + } + + *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; + } + } + + static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + 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; + *r_ret = Variant(); + } else { + *r_ret = ResourceLoader::load(*p_args[0]); + } + } + + static inline void inst2dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(1); + + if (p_args[0]->get_type() == Variant::NIL) { + *r_ret = Variant(); + } else if (p_args[0]->get_type() != Variant::OBJECT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + 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; + *r_ret = RTR("Not based on a script"); + return; + } + + GDScript *p = base.ptr(); + Vector<StringName> sname; + + while (p->_owner) { + sname.push_back(p->name); + p = p->_owner; + } + sname.invert(); + + if (!p->path.is_resource_file()) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + *r_ret = Variant(); + + *r_ret = RTR("Not based on a resource file"); + + return; + } + + NodePath cp(sname, Vector<StringName>(), false); + + Dictionary d; + d["@subpath"] = cp; + d["@path"] = p->get_path(); + + for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) { + if (!d.has(E->key())) { + d[E->key()] = ins->members[E->get().index]; + } + } + *r_ret = d; + } + } + } + + static inline void dict2inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + 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; + *r_ret = Variant(); + + return; + } + + 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; + *r_ret = RTR("Invalid instance dictionary format (missing @path)"); + + return; + } + + 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; + *r_ret = RTR("Invalid instance dictionary format (can't load script at @path)"); + return; + } + + 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; + *r_ret = Variant(); + *r_ret = RTR("Invalid instance dictionary format (invalid script at @path)"); + return; + } + + NodePath sub; + if (d.has("@subpath")) { + sub = d["@subpath"]; + } + + 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; + *r_ret = Variant(); + *r_ret = RTR("Invalid instance dictionary (invalid subclasses)"); + return; + } + } + *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, 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(); + + for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { + if (d.has(E->key())) { + ins->members.write[E->get().index] = d[E->key()]; + } + } + } + + static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + if (p_arg_count < 3) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 3; + *r_ret = Variant(); + return; + } + if (p_arg_count > 4) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_error.argument = 4; + *r_ret = Variant(); + return; + } + + VALIDATE_ARG_INT(0); + VALIDATE_ARG_INT(1); + VALIDATE_ARG_INT(2); + + Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f); + + if (p_arg_count == 4) { + VALIDATE_ARG_INT(3); + color.a = (int64_t)*p_args[3] / 255.0f; + } + + *r_ret = color; + } + + static inline void print_debug(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + String str; + for (int i = 0; i < p_arg_count; i++) { + str += p_args[i]->operator String(); + } + + ScriptLanguage *script = GDScriptLanguage::get_singleton(); + if (script->debug_get_stack_level_count() > 0) { + str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; + } + + print_line(str); + *r_ret = Variant(); + } + + static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(0); + + 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) + "'"); + }; + } + + static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + VALIDATE_ARG_COUNT(0); + + 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); + frame["line"] = script->debug_get_stack_level_line(i); + ret.push_back(frame); + }; + *r_ret = ret; + } + + static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { + 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; + default: { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type())); + } + } + } +}; + +struct GDScriptUtilityFunctionInfo { + GDScriptUtilityFunctions::FunctionPtr function; + MethodInfo info; + bool is_constant = false; +}; + +static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table; +static List<StringName> utility_function_name_table; + +static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) { + StringName sname(p_name); + + ERR_FAIL_COND(utility_function_table.has(sname)); + + GDScriptUtilityFunctionInfo function; + function.function = p_function; + function.info = p_method_info; + function.is_constant = p_is_const; + + utility_function_table.insert(sname, function); + utility_function_name_table.push_back(sname); +} + +#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = m_return_type; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name); \ + info.return_val.type = m_return_type; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name); \ + info.return_val.type = m_return_type; \ + info.flags |= METHOD_FLAG_VARARG; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = Variant::NIL; \ + info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = Variant::OBJECT; \ + info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \ + info.return_val.class_name = m_return_type; \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \ + { \ + String name(#m_func); \ + if (name.begins_with("_")) { \ + name = name.substr(1, name.length() - 1); \ + } \ + MethodInfo info = MethodInfo(name, __VA_ARGS__); \ + info.return_val.type = m_return_type; \ + info.default_arguments.push_back(m_default); \ + _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \ + } + +#define ARG(m_name, m_type) \ + PropertyInfo(m_type, m_name) + +#define VARARG(m_name) \ + PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT) + +void GDScriptUtilityFunctions::register_functions() { + REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT)); + REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME)); + REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT)); + REGISTER_VARARG_FUNC(str, true, Variant::STRING); + REGISTER_VARARG_FUNC(range, false, Variant::ARRAY); + REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING)); + REGISTER_FUNC(inst2dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT)); + REGISTER_FUNC(dict2inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY)); + REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT)); + REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL); + REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL); + REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY); + REGISTER_FUNC(len, true, Variant::INT, VARARG("var")); +} + +void GDScriptUtilityFunctions::unregister_functions() { + utility_function_name_table.clear(); + utility_function_table.clear(); +} + +GDScriptUtilityFunctions::FunctionPtr GDScriptUtilityFunctions::get_function(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, nullptr); + return info->function; +} + +bool GDScriptUtilityFunctions::has_function_return_value(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, false); + return info->info.return_val.type != Variant::NIL || bool(info->info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); +} + +Variant::Type GDScriptUtilityFunctions::get_function_return_type(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, Variant::NIL); + return info->info.return_val.type; +} + +StringName GDScriptUtilityFunctions::get_function_return_class(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, StringName()); + return info->info.return_val.class_name; +} + +Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringName &p_function, int p_arg) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL); + return info->info.arguments[p_arg].type; +} + +int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, 0); + return info->info.arguments.size(); +} + +bool GDScriptUtilityFunctions::is_function_vararg(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, false); + return (bool)(info->info.flags & METHOD_FLAG_VARARG); +} + +bool GDScriptUtilityFunctions::is_function_constant(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, false); + return info->is_constant; +} + +bool GDScriptUtilityFunctions::function_exists(const StringName &p_function) { + return utility_function_table.has(p_function); +} + +void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions) { + for (const List<StringName>::Element *E = utility_function_name_table.front(); E; E = E->next()) { + r_functions->push_back(E->get()); + } +} + +MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) { + GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); + ERR_FAIL_COND_V(!info, MethodInfo()); + return info->info; +} diff --git a/modules/gdscript/gdscript_utility_functions.h b/modules/gdscript/gdscript_utility_functions.h new file mode 100644 index 0000000000..c6d3718844 --- /dev/null +++ b/modules/gdscript/gdscript_utility_functions.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* gdscript_utility_functions.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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_UTILITY_FUNCTIONS_H +#define GDSCRIPT_UTILITY_FUNCTIONS_H + +#include "core/string/string_name.h" +#include "core/variant/variant.h" + +class GDScriptUtilityFunctions { +public: + typedef void (*FunctionPtr)(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error); + + static FunctionPtr get_function(const StringName &p_function); + static bool has_function_return_value(const StringName &p_function); + static Variant::Type get_function_return_type(const StringName &p_function); + static StringName get_function_return_class(const StringName &p_function); + static Variant::Type get_function_argument_type(const StringName &p_function, int p_arg); + static int get_function_argument_count(const StringName &p_function, int p_arg); + static bool is_function_vararg(const StringName &p_function); + static bool is_function_constant(const StringName &p_function); + + static bool function_exists(const StringName &p_function); + static void get_function_list(List<StringName> *r_functions); + static MethodInfo get_function_info(const StringName &p_function); + + static void register_functions(); + static void unregister_functions(); +}; + +#endif // GDSCRIPT_UTILITY_FUNCTIONS_H diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp new file mode 100644 index 0000000000..4e098d7a6d --- /dev/null +++ b/modules/gdscript/gdscript_vm.cpp @@ -0,0 +1,2918 @@ +/*************************************************************************/ +/* gdscript_vm.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_function.h" + +#include "core/core_string_names.h" +#include "core/os/os.h" +#include "gdscript.h" + +Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { + int address = p_address & ADDR_MASK; + + //sequential table (jump table generated by compiler) + switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) { + case ADDR_TYPE_SELF: { +#ifdef DEBUG_ENABLED + if (unlikely(!p_instance)) { + r_error = "Cannot access self without instance."; + return nullptr; + } +#endif + return &self; + } break; + case ADDR_TYPE_CLASS: { + return &static_ref; + } break; + case ADDR_TYPE_MEMBER: { +#ifdef DEBUG_ENABLED + if (unlikely(!p_instance)) { + r_error = "Cannot access member without instance."; + return nullptr; + } +#endif + //member indexing is O(1) + return &p_instance->members.write[address]; + } break; + case ADDR_TYPE_CLASS_CONSTANT: { + //todo change to index! + GDScript *s = p_script; +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); +#endif + const StringName *sn = &_global_names_ptr[address]; + + while (s) { + GDScript *o = s; + while (o) { + Map<StringName, Variant>::Element *E = o->constants.find(*sn); + if (E) { + return &E->get(); + } + o = o->_owner; + } + s = s->_base; + } + + ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug."); + } break; + case ADDR_TYPE_LOCAL_CONSTANT: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _constant_count, nullptr); +#endif + return &_constants_ptr[address]; + } break; + case ADDR_TYPE_STACK: + case ADDR_TYPE_STACK_VARIABLE: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _stack_size, nullptr); +#endif + return &p_stack[address]; + } break; + case ADDR_TYPE_GLOBAL: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr); +#endif + return &GDScriptLanguage::get_singleton()->get_global_array()[address]; + } break; +#ifdef TOOLS_ENABLED + case ADDR_TYPE_NAMED_GLOBAL: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); +#endif + StringName id = _global_names_ptr[address]; + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { + return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; + } else { + r_error = "Autoload singleton '" + String(id) + "' has been removed."; + return nullptr; + } + } break; +#endif + case ADDR_TYPE_NIL: { + return &nil; + } break; + } + + ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); + return nullptr; +} + +#ifdef DEBUG_ENABLED +static String _get_var_type(const Variant *p_var) { + String basestr; + + if (p_var->get_type() == Variant::OBJECT) { + bool was_freed; + Object *bobj = p_var->get_validated_object_with_check(was_freed); + if (!bobj) { + if (was_freed) { + basestr = "null instance"; + } else { + basestr = "previously freed"; + } + } else { + if (bobj->get_script_instance()) { + basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; + } else { + basestr = bobj->get_class(); + } + } + + } else { + basestr = Variant::get_type_name(p_var->get_type()); + } + + return basestr; +} +#endif // DEBUG_ENABLED + +String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { + String err_text; + + if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { + int errorarg = p_err.argument; + // Handle the Object to Object case separately as we don't have further class details. +#ifdef DEBUG_ENABLED + if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { + err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; + } else +#endif // DEBUG_ENABLED + { + err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; + } + } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { + err_text = "Invalid call. Nonexistent " + p_where + "."; + } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { + err_text = "Attempt to call " + p_where + " on a null instance."; + } else { + err_text = "Bug, call error: #" + itos(p_err.error); + } + + return err_text; +} + +#if defined(__GNUC__) +#define OPCODES_TABLE \ + static const void *switch_table_ops[] = { \ + &&OPCODE_OPERATOR, \ + &&OPCODE_OPERATOR_VALIDATED, \ + &&OPCODE_EXTENDS_TEST, \ + &&OPCODE_IS_BUILTIN, \ + &&OPCODE_SET_KEYED, \ + &&OPCODE_SET_KEYED_VALIDATED, \ + &&OPCODE_SET_INDEXED_VALIDATED, \ + &&OPCODE_GET_KEYED, \ + &&OPCODE_GET_KEYED_VALIDATED, \ + &&OPCODE_GET_INDEXED_VALIDATED, \ + &&OPCODE_SET_NAMED, \ + &&OPCODE_SET_NAMED_VALIDATED, \ + &&OPCODE_GET_NAMED, \ + &&OPCODE_GET_NAMED_VALIDATED, \ + &&OPCODE_SET_MEMBER, \ + &&OPCODE_GET_MEMBER, \ + &&OPCODE_ASSIGN, \ + &&OPCODE_ASSIGN_TRUE, \ + &&OPCODE_ASSIGN_FALSE, \ + &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_NATIVE, \ + &&OPCODE_ASSIGN_TYPED_SCRIPT, \ + &&OPCODE_CAST_TO_BUILTIN, \ + &&OPCODE_CAST_TO_NATIVE, \ + &&OPCODE_CAST_TO_SCRIPT, \ + &&OPCODE_CONSTRUCT, \ + &&OPCODE_CONSTRUCT_VALIDATED, \ + &&OPCODE_CONSTRUCT_ARRAY, \ + &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CALL, \ + &&OPCODE_CALL_RETURN, \ + &&OPCODE_CALL_ASYNC, \ + &&OPCODE_CALL_UTILITY, \ + &&OPCODE_CALL_UTILITY_VALIDATED, \ + &&OPCODE_CALL_GDSCRIPT_UTILITY, \ + &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ + &&OPCODE_CALL_SELF_BASE, \ + &&OPCODE_CALL_METHOD_BIND, \ + &&OPCODE_CALL_METHOD_BIND_RET, \ + &&OPCODE_CALL_PTRCALL_NO_RETURN, \ + &&OPCODE_CALL_PTRCALL_BOOL, \ + &&OPCODE_CALL_PTRCALL_INT, \ + &&OPCODE_CALL_PTRCALL_FLOAT, \ + &&OPCODE_CALL_PTRCALL_STRING, \ + &&OPCODE_CALL_PTRCALL_VECTOR2, \ + &&OPCODE_CALL_PTRCALL_VECTOR2I, \ + &&OPCODE_CALL_PTRCALL_RECT2, \ + &&OPCODE_CALL_PTRCALL_RECT2I, \ + &&OPCODE_CALL_PTRCALL_VECTOR3, \ + &&OPCODE_CALL_PTRCALL_VECTOR3I, \ + &&OPCODE_CALL_PTRCALL_TRANSFORM2D, \ + &&OPCODE_CALL_PTRCALL_PLANE, \ + &&OPCODE_CALL_PTRCALL_QUAT, \ + &&OPCODE_CALL_PTRCALL_AABB, \ + &&OPCODE_CALL_PTRCALL_BASIS, \ + &&OPCODE_CALL_PTRCALL_TRANSFORM, \ + &&OPCODE_CALL_PTRCALL_COLOR, \ + &&OPCODE_CALL_PTRCALL_STRING_NAME, \ + &&OPCODE_CALL_PTRCALL_NODE_PATH, \ + &&OPCODE_CALL_PTRCALL_RID, \ + &&OPCODE_CALL_PTRCALL_OBJECT, \ + &&OPCODE_CALL_PTRCALL_CALLABLE, \ + &&OPCODE_CALL_PTRCALL_SIGNAL, \ + &&OPCODE_CALL_PTRCALL_DICTIONARY, \ + &&OPCODE_CALL_PTRCALL_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \ + &&OPCODE_AWAIT, \ + &&OPCODE_AWAIT_RESUME, \ + &&OPCODE_JUMP, \ + &&OPCODE_JUMP_IF, \ + &&OPCODE_JUMP_IF_NOT, \ + &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ + &&OPCODE_RETURN, \ + &&OPCODE_ITERATE_BEGIN, \ + &&OPCODE_ITERATE_BEGIN_INT, \ + &&OPCODE_ITERATE_BEGIN_FLOAT, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ + &&OPCODE_ITERATE_BEGIN_STRING, \ + &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ + &&OPCODE_ITERATE_BEGIN_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_OBJECT, \ + &&OPCODE_ITERATE, \ + &&OPCODE_ITERATE_INT, \ + &&OPCODE_ITERATE_FLOAT, \ + &&OPCODE_ITERATE_VECTOR2, \ + &&OPCODE_ITERATE_VECTOR2I, \ + &&OPCODE_ITERATE_VECTOR3, \ + &&OPCODE_ITERATE_VECTOR3I, \ + &&OPCODE_ITERATE_STRING, \ + &&OPCODE_ITERATE_DICTIONARY, \ + &&OPCODE_ITERATE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_ASSERT, \ + &&OPCODE_BREAKPOINT, \ + &&OPCODE_LINE, \ + &&OPCODE_END \ + }; \ + static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum."); + +#define OPCODE(m_op) \ + m_op: +#define OPCODE_WHILE(m_test) \ + OPSWHILE: +#define OPCODES_END \ + OPSEXIT: +#define OPCODES_OUT \ + OPSOUT: +#define DISPATCH_OPCODE goto OPSWHILE +#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test]; +#define OPCODE_BREAK goto OPSEXIT +#define OPCODE_OUT goto OPSOUT +#else +#define OPCODES_TABLE +#define OPCODE(m_op) case m_op: +#define OPCODE_WHILE(m_test) while (m_test) +#define OPCODES_END +#define OPCODES_OUT +#define DISPATCH_OPCODE continue +#define OPCODE_SWITCH(m_test) switch (m_test) +#define OPCODE_BREAK break +#define OPCODE_OUT break +#endif + +// Helpers for VariantInternal methods in macros. +#define OP_GET_BOOL get_bool +#define OP_GET_INT get_int +#define OP_GET_FLOAT get_float +#define OP_GET_VECTOR2 get_vector2 +#define OP_GET_VECTOR2I get_vector2i +#define OP_GET_VECTOR3 get_vector3 +#define OP_GET_VECTOR3I get_vector3i +#define OP_GET_RECT2 get_rect2 +#define OP_GET_RECT2I get_rect2i +#define OP_GET_QUAT get_quat +#define OP_GET_COLOR get_color +#define OP_GET_STRING get_string +#define OP_GET_STRING_NAME get_string_name +#define OP_GET_NODE_PATH get_node_path +#define OP_GET_CALLABLE get_callable +#define OP_GET_SIGNAL get_signal +#define OP_GET_ARRAY get_array +#define OP_GET_DICTIONARY get_dictionary +#define OP_GET_PACKED_BYTE_ARRAY get_byte_array +#define OP_GET_PACKED_INT32_ARRAY get_int32_array +#define OP_GET_PACKED_INT64_ARRAY get_int64_array +#define OP_GET_PACKED_FLOAT32_ARRAY get_float32_array +#define OP_GET_PACKED_FLOAT64_ARRAY get_float64_array +#define OP_GET_PACKED_STRING_ARRAY get_string_array +#define OP_GET_PACKED_VECTOR2_ARRAY get_vector2_array +#define OP_GET_PACKED_VECTOR3_ARRAY get_vector3_array +#define OP_GET_PACKED_COLOR_ARRAY get_color_array +#define OP_GET_TRANSFORM get_transform +#define OP_GET_TRANSFORM2D get_transform2d +#define OP_GET_PLANE get_plane +#define OP_GET_AABB get_aabb +#define OP_GET_BASIS get_basis +#define OP_GET_RID get_rid + +Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) { + OPCODES_TABLE; + + if (!_code_ptr) { + return Variant(); + } + + r_err.error = Callable::CallError::CALL_OK; + + Variant self; + Variant static_ref; + Variant retvalue; + Variant *stack = nullptr; + Variant **instruction_args; + const void **call_args_ptr = nullptr; + int defarg = 0; + +#ifdef DEBUG_ENABLED + + //GDScriptLanguage::get_singleton()->calls++; + +#endif + + uint32_t alloca_size = 0; + GDScript *script; + int ip = 0; + int line = _initial_line; + + if (p_state) { + //use existing (supplied) state (awaited) + stack = (Variant *)p_state->stack.ptr(); + instruction_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check + line = p_state->line; + ip = p_state->ip; + alloca_size = p_state->stack.size(); + script = p_state->script; + p_instance = p_state->instance; + defarg = p_state->defarg; + self = p_state->self; + + } else { + if (p_argcount != _argument_count) { + if (p_argcount > _argument_count) { + r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; + r_err.argument = _argument_count; + + return Variant(); + } else if (p_argcount < _argument_count - _default_arg_count) { + r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_err.argument = _argument_count - _default_arg_count; + return Variant(); + } else { + defarg = _argument_count - p_argcount; + } + } + + alloca_size = sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size; + + if (alloca_size) { + uint8_t *aptr = (uint8_t *)alloca(alloca_size); + + if (_stack_size) { + stack = (Variant *)aptr; + for (int i = 0; i < p_argcount; i++) { + if (!argument_types[i].has_type) { + memnew_placement(&stack[i], Variant(*p_args[i])); + continue; + } + + if (!argument_types[i].is_type(*p_args[i], true)) { + r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; + return Variant(); + } + if (argument_types[i].kind == GDScriptDataType::BUILTIN) { + Variant arg; + Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); + memnew_placement(&stack[i], Variant(arg)); + } else { + memnew_placement(&stack[i], Variant(*p_args[i])); + } + } + for (int i = p_argcount; i < _stack_size; i++) { + memnew_placement(&stack[i], Variant); + } + } else { + stack = nullptr; + } + + if (_instruction_args_size) { + instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; + } else { + instruction_args = nullptr; + } + + } else { + stack = nullptr; + instruction_args = nullptr; + } + + if (p_instance) { + self = p_instance->owner; + script = p_instance->script.ptr(); + } else { + script = _script; + } + } + if (_ptrcall_args_size) { + call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *)); + } else { + call_args_ptr = nullptr; + } + + static_ref = script; + + String err_text; + +#ifdef DEBUG_ENABLED + + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line); + } + +#define GD_ERR_BREAK(m_cond) \ + { \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \ + OPCODE_BREAK; \ + } \ + } + +#define CHECK_SPACE(m_space) \ + GD_ERR_BREAK((ip + m_space) > _code_size) + +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \ + if (unlikely(!m_v)) \ + OPCODE_BREAK; + +#else +#define GD_ERR_BREAK(m_cond) +#define CHECK_SPACE(m_space) +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); + +#endif + +#define GET_INSTRUCTION_ARG(m_v, m_idx) \ + Variant *m_v = instruction_args[m_idx] + +#ifdef DEBUG_ENABLED + + uint64_t function_start_time = 0; + uint64_t function_call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + function_start_time = OS::get_singleton()->get_ticks_usec(); + function_call_time = 0; + profile.call_count++; + profile.frame_call_count++; + } + bool exit_ok = false; + bool awaited = false; +#endif + +#ifdef DEBUG_ENABLED + OPCODE_WHILE(ip < _code_size) { + int last_opcode = _code_ptr[ip]; +#else + OPCODE_WHILE(true) { +#endif + // Load arguments for the instruction before each instruction. + int instr_arg_count = ((_code_ptr[ip]) & INSTR_ARGS_MASK) >> INSTR_BITS; + for (int i = 0; i < instr_arg_count; i++) { + GET_VARIANT_PTR(v, i + 1); + instruction_args[i] = v; + } + + OPCODE_SWITCH(_code_ptr[ip] & INSTR_MASK) { + OPCODE(OPCODE_OPERATOR) { + CHECK_SPACE(5); + + bool valid; + Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4]; + GD_ERR_BREAK(op >= Variant::OP_MAX); + + GET_INSTRUCTION_ARG(a, 0); + GET_INSTRUCTION_ARG(b, 1); + GET_INSTRUCTION_ARG(dst, 2); + +#ifdef DEBUG_ENABLED + + Variant ret; + Variant::evaluate(op, *a, *b, ret, valid); +#else + Variant::evaluate(op, *a, *b, *dst, valid); +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + if (ret.get_type() == Variant::STRING) { + //return a string when invalid with the error + err_text = ret; + err_text += " in operator '" + Variant::get_operator_name(op) + "'."; + } else { + err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'."; + } + OPCODE_BREAK; + } + *dst = ret; +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_OPERATOR_VALIDATED) { + CHECK_SPACE(5); + + int operator_idx = _code_ptr[ip + 4]; + GD_ERR_BREAK(operator_idx < 0 || operator_idx >= _operator_funcs_count); + Variant::ValidatedOperatorEvaluator operator_func = _operator_funcs_ptr[operator_idx]; + + GET_INSTRUCTION_ARG(a, 0); + GET_INSTRUCTION_ARG(b, 1); + GET_INSTRUCTION_ARG(dst, 2); + + operator_func(a, b, dst); + + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_EXTENDS_TEST) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(a, 0); + GET_INSTRUCTION_ARG(b, 1); + GET_INSTRUCTION_ARG(dst, 2); + +#ifdef DEBUG_ENABLED + if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { + err_text = "Right operand of 'is' is not a class."; + OPCODE_BREAK; + } +#endif + + bool extends_ok = false; + if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) { +#ifdef DEBUG_ENABLED + bool was_freed; + Object *obj_A = a->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Left operand of 'is' is a previously freed instance."; + OPCODE_BREAK; + } + + Object *obj_B = b->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Right operand of 'is' is a previously freed instance."; + OPCODE_BREAK; + } +#else + + Object *obj_A = *a; + Object *obj_B = *b; +#endif // DEBUG_ENABLED + + GDScript *scr_B = Object::cast_to<GDScript>(obj_B); + + if (scr_B) { + //if B is a script, the only valid condition is that A has an instance which inherits from the script + //in other situation, this shoul return false. + + if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { + GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); + //bool found=false; + while (cmp) { + if (cmp == scr_B) { + //inherits from script, all ok + extends_ok = true; + break; + } + + cmp = cmp->_base; + } + } + + } else { + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); + +#ifdef DEBUG_ENABLED + if (!nc) { + err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; + OPCODE_BREAK; + } +#endif + extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); + } + } + + *dst = extends_ok; + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_IS_BUILTIN) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(value, 0); + GET_INSTRUCTION_ARG(dst, 1); + Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; + + GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + + *dst = value->get_type() == var_type; + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_KEYED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(value, 2); + + bool valid; + dst->set(*index, *value, &valid); + +#ifdef DEBUG_ENABLED + if (!valid) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; + OPCODE_BREAK; + } +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_KEYED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(value, 2); + + int index_setter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_setter < 0 || index_setter >= _keyed_setters_count); + const Variant::ValidatedKeyedSetter setter = _keyed_setters_ptr[index_setter]; + + bool valid; + setter(dst, index, value, &valid); + +#ifdef DEBUG_ENABLED + if (!valid) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; + OPCODE_BREAK; + } +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_INDEXED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(value, 2); + + int index_setter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_setter < 0 || index_setter >= _indexed_setters_count); + const Variant::ValidatedIndexedSetter setter = _indexed_setters_ptr[index_setter]; + + int64_t int_index = *VariantInternal::get_int(index); + + bool oob; + setter(dst, int_index, value, &oob); + +#ifdef DEBUG_ENABLED + if (oob) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Out of bounds set index " + v + " (on base: '" + _get_var_type(dst) + "')"; + OPCODE_BREAK; + } +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_KEYED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(dst, 2); + + bool valid; +#ifdef DEBUG_ENABLED + // Allow better error message in cases where src and dst are the same stack position. + Variant ret = src->get(*index, &valid); +#else + *dst = src->get(*index, &valid); + +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; + OPCODE_BREAK; + } + *dst = ret; +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_KEYED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(key, 1); + GET_INSTRUCTION_ARG(dst, 2); + + int index_getter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_getter < 0 || index_getter >= _keyed_getters_count); + const Variant::ValidatedKeyedGetter getter = _keyed_getters_ptr[index_getter]; + + bool valid; +#ifdef DEBUG_ENABLED + // Allow better error message in cases where src and dst are the same stack position. + Variant ret; + getter(src, key, &ret, &valid); +#else + getter(src, key, dst, &valid); +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + String v = key->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(key) + "'"; + } + err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; + OPCODE_BREAK; + } + *dst = ret; +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_INDEXED_VALIDATED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(index, 1); + GET_INSTRUCTION_ARG(dst, 2); + + int index_getter = _code_ptr[ip + 4]; + GD_ERR_BREAK(index_getter < 0 || index_getter >= _indexed_getters_count); + const Variant::ValidatedIndexedGetter getter = _indexed_getters_ptr[index_getter]; + + int64_t int_index = *VariantInternal::get_int(index); + + bool oob; + getter(src, int_index, dst, &oob); + +#ifdef DEBUG_ENABLED + if (oob) { + String v = index->operator String(); + if (v != "") { + v = "'" + v + "'"; + } else { + v = "of type '" + _get_var_type(index) + "'"; + } + err_text = "Out of bounds get index " + v + " (on base: '" + _get_var_type(src) + "')"; + OPCODE_BREAK; + } +#endif + ip += 5; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_NAMED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(value, 1); + + int indexname = _code_ptr[ip + 3]; + + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; + dst->set_named(*index, *value, valid); + +#ifdef DEBUG_ENABLED + if (!valid) { + String err_type; + err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + OPCODE_BREAK; + } +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_NAMED_VALIDATED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(value, 1); + + int index_setter = _code_ptr[ip + 3]; + GD_ERR_BREAK(index_setter < 0 || index_setter >= _setters_count); + const Variant::ValidatedSetter setter = _setters_ptr[index_setter]; + + setter(dst, value); + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_NAMED) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + + int indexname = _code_ptr[ip + 3]; + + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; +#ifdef DEBUG_ENABLED + //allow better error message in cases where src and dst are the same stack position + Variant ret = src->get_named(*index, valid); + +#else + *dst = src->get_named(*index, valid); +#endif +#ifdef DEBUG_ENABLED + if (!valid) { + if (src->has_method(*index)) { + err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' or funcref(obj, \"" + index->operator String() + "\") ?"; + } else { + err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "')."; + } + OPCODE_BREAK; + } + *dst = ret; +#endif + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_NAMED_VALIDATED) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + + int index_getter = _code_ptr[ip + 3]; + GD_ERR_BREAK(index_getter < 0 || index_getter >= _getters_count); + const Variant::ValidatedGetter getter = _getters_ptr[index_getter]; + + getter(src, dst); + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_SET_MEMBER) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(src, 0); + int indexname = _code_ptr[ip + 2]; + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; + + bool valid; +#ifndef DEBUG_ENABLED + ClassDB::set_property(p_instance->owner, *index, *src, &valid); +#else + bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid); + if (!ok) { + err_text = "Internal error setting property: " + String(*index); + OPCODE_BREAK; + } else if (!valid) { + err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + "."; + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_MEMBER) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(dst, 0); + int indexname = _code_ptr[ip + 2]; + GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); + const StringName *index = &_global_names_ptr[indexname]; +#ifndef DEBUG_ENABLED + ClassDB::get_property(p_instance->owner, *index, *dst); +#else + bool ok = ClassDB::get_property(p_instance->owner, *index, *dst); + if (!ok) { + err_text = "Internal error getting property: " + String(*index); + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + + *dst = *src; + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TRUE) { + CHECK_SPACE(2); + GET_INSTRUCTION_ARG(dst, 0); + + *dst = true; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_FALSE) { + CHECK_SPACE(2); + GET_INSTRUCTION_ARG(dst, 0); + + *dst = false; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + + Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; + GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); + + if (src->get_type() != var_type) { +#ifdef DEBUG_ENABLED + if (Variant::can_convert_strict(src->get_type(), var_type)) { +#endif // DEBUG_ENABLED + Callable::CallError ce; + Variant::construct(var_type, *dst, const_cast<const Variant **>(&src), 1, ce); + } else { +#ifdef DEBUG_ENABLED + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; + OPCODE_BREAK; + } + } else { +#endif // DEBUG_ENABLED + *dst = *src; + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + +#ifdef DEBUG_ENABLED + GET_INSTRUCTION_ARG(type, 2); + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); + GD_ERR_BREAK(!nc); + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } + Object *src_obj = src->operator Object *(); + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + err_text = "Trying to assign value of type '" + src_obj->get_class_name() + + "' to a variable of type '" + nc->get_name() + "'."; + OPCODE_BREAK; + } +#endif // DEBUG_ENABLED + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + +#ifdef DEBUG_ENABLED + GET_INSTRUCTION_ARG(type, 2); + Script *base_type = Object::cast_to<Script>(type->operator Object *()); + + GD_ERR_BREAK(!base_type); + + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + if (!scr_inst) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + bool valid = false; + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + + if (!valid) { + err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + + "' to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } + } +#endif // DEBUG_ENABLED + + *dst = *src; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_BUILTIN) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + Variant::Type to_type = (Variant::Type)_code_ptr[ip + 3]; + + GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); + + Callable::CallError err; + Variant::construct(to_type, *dst, (const Variant **)&src, 1, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'."; + OPCODE_BREAK; + } +#endif + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_NATIVE) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + GET_INSTRUCTION_ARG(to_type, 2); + + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); + GD_ERR_BREAK(!nc); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Invalid cast: can't convert a non-object value to an object type."; + OPCODE_BREAK; + } +#endif + Object *src_obj = src->operator Object *(); + + if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { + *dst = Variant(); // invalid cast, assign NULL + } else { + *dst = *src; + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CAST_TO_SCRIPT) { + CHECK_SPACE(4); + GET_INSTRUCTION_ARG(src, 0); + GET_INSTRUCTION_ARG(dst, 1); + GET_INSTRUCTION_ARG(to_type, 2); + + Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); + + GD_ERR_BREAK(!base_type); + +#ifdef DEBUG_ENABLED + if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { + err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; + OPCODE_BREAK; + } +#endif + + bool valid = false; + + if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { + ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); + + if (scr_inst) { + Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); + + while (src_type) { + if (src_type == base_type) { + valid = true; + break; + } + src_type = src_type->get_base_script().ptr(); + } + } + } + + if (valid) { + *dst = *src; // Valid cast, copy the source object + } else { + *dst = Variant(); // invalid cast, assign NULL + } + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + Variant::Type t = Variant::Type(_code_ptr[ip + 2]); + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + Callable::CallError err; + Variant::construct(t, *dst, (const Variant **)argptrs, argc, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs); + OPCODE_BREAK; + } +#endif + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT_VALIDATED) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + int constructor_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(constructor_idx < 0 || constructor_idx >= _constructors_count); + Variant::ValidatedConstructor constructor = _constructors_ptr[constructor_idx]; + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + constructor(dst, (const Variant **)argptrs); + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT_ARRAY) { + CHECK_SPACE(1 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + Array array; + array.resize(argc); + + for (int i = 0; i < argc; i++) { + array[i] = *(instruction_args[i]); + } + + GET_INSTRUCTION_ARG(dst, argc); + + *dst = array; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + Dictionary dict; + + for (int i = 0; i < argc; i++) { + GET_INSTRUCTION_ARG(k, i * 2 + 0); + GET_INSTRUCTION_ARG(v, i * 2 + 1); + dict[*k] = *v; + } + + GET_INSTRUCTION_ARG(dst, argc * 2); + + *dst = dict; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_ASYNC) + OPCODE(OPCODE_CALL_RETURN) + OPCODE(OPCODE_CALL) { + CHECK_SPACE(3 + instr_arg_count); + bool call_ret = (_code_ptr[ip] & INSTR_MASK) != OPCODE_CALL; +#ifdef DEBUG_ENABLED + bool call_async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC; +#endif + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + int methodname_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(methodname_idx < 0 || methodname_idx >= _global_names_count); + const StringName *methodname = &_global_names_ptr[methodname_idx]; + + GET_INSTRUCTION_ARG(base, argc); + Variant **argptrs = instruction_args; + +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + call_time = OS::get_singleton()->get_ticks_usec(); + } + +#endif + Callable::CallError err; + if (call_ret) { + GET_INSTRUCTION_ARG(ret, argc + 1); + base->call(*methodname, (const Variant **)argptrs, argc, *ret, err); +#ifdef DEBUG_ENABLED + if (!call_async && ret->get_type() == Variant::OBJECT) { + // Check if getting a function state without await. + bool was_freed = false; + Object *obj = ret->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Got a freed object as a result of the call."; + OPCODE_BREAK; + } + if (obj && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { + err_text = R"(Trying to call an async function without "await".)"; + OPCODE_BREAK; + } + } +#endif + } else { + Variant ret; + base->call(*methodname, (const Variant **)argptrs, argc, ret, err); + } +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; + } + + if (err.error != Callable::CallError::CALL_OK) { + String methodstr = *methodname; + String basestr = _get_var_type(base); + + if (methodstr == "call") { + if (argc >= 1) { + methodstr = String(*argptrs[0]) + " (via call)"; + if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { + err.argument += 1; + } + } + } else if (methodstr == "free") { + if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { + if (base->is_ref()) { + err_text = "Attempted to free a reference."; + OPCODE_BREAK; + } else if (base->get_type() == Variant::OBJECT) { + err_text = "Attempted to free a locked object (calling or emitting)."; + OPCODE_BREAK; + } + } + } else if (methodstr == "call_recursive" && basestr == "TreeItem") { + if (argc >= 1) { + methodstr = String(*argptrs[0]) + " (via TreeItem.call_recursive)"; + if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { + err.argument += 1; + } + } + } + err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); + OPCODE_BREAK; + } +#endif + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_METHOD_BIND) + OPCODE(OPCODE_CALL_METHOD_BIND_RET) { + CHECK_SPACE(3 + instr_arg_count); + bool call_ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET; + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); + MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; + + GET_INSTRUCTION_ARG(base, argc); + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *base_obj = base->get_validated_object_with_check(freed); + if (freed) { + err_text = "Trying to call a function on a previously freed instance."; + OPCODE_BREAK; + } else if (!base_obj) { + err_text = "Trying to call a function on a null value."; + OPCODE_BREAK; + } +#else + Object *base_obj = base->operator Object *(); +#endif + Variant **argptrs = instruction_args; + +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + Callable::CallError err; + if (call_ret) { + GET_INSTRUCTION_ARG(ret, argc + 1); + *ret = method->call(base_obj, (const Variant **)argptrs, argc, err); + } else { + method->call(base_obj, (const Variant **)argptrs, argc, err); + } + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; + } + + if (err.error != Callable::CallError::CALL_OK) { + String methodstr = method->get_name(); + String basestr = _get_var_type(base); + + if (methodstr == "call") { + if (argc >= 1) { + methodstr = String(*argptrs[0]) + " (via call)"; + if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { + err.argument += 1; + } + } + } else if (methodstr == "free") { + if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { + if (base->is_ref()) { + err_text = "Attempted to free a reference."; + OPCODE_BREAK; + } else if (base->get_type() == Variant::OBJECT) { + err_text = "Attempted to free a locked object (calling or emitting)."; + OPCODE_BREAK; + } + } + } + err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + +#ifdef DEBUG_ENABLED +#define OPCODE_CALL_PTR(m_type) \ + OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ + CHECK_SPACE(3 + instr_arg_count); \ + ip += instr_arg_count; \ + int argc = _code_ptr[ip + 1]; \ + GD_ERR_BREAK(argc < 0); \ + GET_INSTRUCTION_ARG(base, argc); \ + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); \ + MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \ + bool freed = false; \ + Object *base_obj = base->get_validated_object_with_check(freed); \ + if (freed) { \ + err_text = "Trying to call a function on a previously freed instance."; \ + OPCODE_BREAK; \ + } else if (!base_obj) { \ + err_text = "Trying to call a function on a null value."; \ + OPCODE_BREAK; \ + } \ + const void **argptrs = call_args_ptr; \ + for (int i = 0; i < argc; i++) { \ + GET_INSTRUCTION_ARG(v, i); \ + argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \ + } \ + uint64_t call_time = 0; \ + if (GDScriptLanguage::get_singleton()->profiling) { \ + call_time = OS::get_singleton()->get_ticks_usec(); \ + } \ + GET_INSTRUCTION_ARG(ret, argc + 1); \ + VariantInternal::initialize(ret, Variant::m_type); \ + void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \ + method->ptrcall(base_obj, argptrs, ret_opaque); \ + if (GDScriptLanguage::get_singleton()->profiling) { \ + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \ + } \ + ip += 3; \ + } \ + DISPATCH_OPCODE +#else +#define OPCODE_CALL_PTR(m_type) \ + OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ + CHECK_SPACE(3 + instr_arg_count); \ + int argc = _code_ptr[ip + 1]; \ + GET_INSTRUCTION_ARG(base, argc); \ + MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \ + Object *base_obj = *VariantInternal::get_object(base); \ + const void **argptrs = call_args_ptr; \ + for (int i = 0; i < argc; i++) { \ + GET_INSTRUCTION_ARG(v, i); \ + argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \ + } \ + GET_INSTRUCTION_ARG(ret, argc + 1); \ + VariantInternal::initialize(ret, Variant::m_type); \ + void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \ + method->ptrcall(base_obj, argptrs, ret_opaque); \ + ip += 3; \ + } \ + DISPATCH_OPCODE +#endif + + OPCODE_CALL_PTR(BOOL); + OPCODE_CALL_PTR(INT); + OPCODE_CALL_PTR(FLOAT); + OPCODE_CALL_PTR(STRING); + OPCODE_CALL_PTR(VECTOR2); + OPCODE_CALL_PTR(VECTOR2I); + OPCODE_CALL_PTR(RECT2); + OPCODE_CALL_PTR(RECT2I); + OPCODE_CALL_PTR(VECTOR3); + OPCODE_CALL_PTR(VECTOR3I); + OPCODE_CALL_PTR(TRANSFORM2D); + OPCODE_CALL_PTR(PLANE); + OPCODE_CALL_PTR(QUAT); + OPCODE_CALL_PTR(AABB); + OPCODE_CALL_PTR(BASIS); + OPCODE_CALL_PTR(TRANSFORM); + OPCODE_CALL_PTR(COLOR); + OPCODE_CALL_PTR(STRING_NAME); + OPCODE_CALL_PTR(NODE_PATH); + OPCODE_CALL_PTR(RID); + OPCODE_CALL_PTR(CALLABLE); + OPCODE_CALL_PTR(SIGNAL); + OPCODE_CALL_PTR(DICTIONARY); + OPCODE_CALL_PTR(ARRAY); + OPCODE_CALL_PTR(PACKED_BYTE_ARRAY); + OPCODE_CALL_PTR(PACKED_INT32_ARRAY); + OPCODE_CALL_PTR(PACKED_INT64_ARRAY); + OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY); + OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY); + OPCODE_CALL_PTR(PACKED_STRING_ARRAY); + OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY); + OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY); + OPCODE_CALL_PTR(PACKED_COLOR_ARRAY); + OPCODE(OPCODE_CALL_PTRCALL_OBJECT) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); + MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; + + GET_INSTRUCTION_ARG(base, argc); +#ifdef DEBUG_ENABLED + bool freed = false; + Object *base_obj = base->get_validated_object_with_check(freed); + if (freed) { + err_text = "Trying to call a function on a previously freed instance."; + OPCODE_BREAK; + } else if (!base_obj) { + err_text = "Trying to call a function on a null value."; + OPCODE_BREAK; + } +#else + Object *base_obj = *VariantInternal::get_object(base); +#endif + + const void **argptrs = call_args_ptr; + + for (int i = 0; i < argc; i++) { + GET_INSTRUCTION_ARG(v, i); + argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); + } +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + GET_INSTRUCTION_ARG(ret, argc + 1); + VariantInternal::initialize(ret, Variant::OBJECT); + Object **ret_opaque = VariantInternal::get_object(ret); + method->ptrcall(base_obj, argptrs, ret_opaque); + VariantInternal::object_assign(ret, *ret_opaque); // Set so ID is correct too. + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); + MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; + + GET_INSTRUCTION_ARG(base, argc); +#ifdef DEBUG_ENABLED + bool freed = false; + Object *base_obj = base->get_validated_object_with_check(freed); + if (freed) { + err_text = "Trying to call a function on a previously freed instance."; + OPCODE_BREAK; + } else if (!base_obj) { + err_text = "Trying to call a function on a null value."; + OPCODE_BREAK; + } +#else + Object *base_obj = *VariantInternal::get_object(base); +#endif + const void **argptrs = call_args_ptr; + + for (int i = 0; i < argc; i++) { + GET_INSTRUCTION_ARG(v, i); + argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); + } +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + GET_INSTRUCTION_ARG(ret, argc + 1); + VariantInternal::initialize(ret, Variant::NIL); + method->ptrcall(base_obj, argptrs, nullptr); + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_BUILTIN_TYPE_VALIDATED) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GET_INSTRUCTION_ARG(base, argc); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _builtin_methods_count); + Variant::ValidatedBuiltInMethod method = _builtin_methods_ptr[_code_ptr[ip + 2]]; + Variant **argptrs = instruction_args; + +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + if (GDScriptLanguage::get_singleton()->profiling) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + GET_INSTRUCTION_ARG(ret, argc + 1); + method(base, (const Variant **)argptrs, argc, ret); + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; + } +#endif + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_UTILITY) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _global_names_count); + StringName function = _global_names_ptr[_code_ptr[ip + 2]]; + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + Callable::CallError err; + Variant::call_utility_function(function, dst, (const Variant **)argptrs, argc, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + String methodstr = function; + if (dst->get_type() == Variant::STRING) { + // Call provided error string. + err_text = "Error calling utility function '" + methodstr + "': " + String(*dst); + } else { + err_text = _get_call_error(err, "utility function '" + methodstr + "'", (const Variant **)argptrs); + } + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_UTILITY_VALIDATED) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _utilities_count); + Variant::ValidatedUtilityFunction function = _utilities_ptr[_code_ptr[ip + 2]]; + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + function(dst, (const Variant **)argptrs, argc); + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_GDSCRIPT_UTILITY) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _gds_utilities_count); + GDScriptUtilityFunctions::FunctionPtr function = _gds_utilities_ptr[_code_ptr[ip + 2]]; + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + Callable::CallError err; + function(dst, (const Variant **)argptrs, argc, err); + +#ifdef DEBUG_ENABLED + if (err.error != Callable::CallError::CALL_OK) { + // TODO: Add this information in debug. + String methodstr = "<unkown function>"; + if (dst->get_type() == Variant::STRING) { + // Call provided error string. + err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst); + } else { + err_text = _get_call_error(err, "GDScript utility function '" + methodstr + "'", (const Variant **)argptrs); + } + OPCODE_BREAK; + } +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CALL_SELF_BASE) { + CHECK_SPACE(3 + instr_arg_count); + + ip += instr_arg_count; + + int self_fun = _code_ptr[ip + 1]; +#ifdef DEBUG_ENABLED + if (self_fun < 0 || self_fun >= _global_names_count) { + err_text = "compiler bug, function name not found"; + OPCODE_BREAK; + } +#endif + const StringName *methodname = &_global_names_ptr[self_fun]; + + int argc = _code_ptr[ip + 2]; + GD_ERR_BREAK(argc < 0); + + Variant **argptrs = instruction_args; + + GET_INSTRUCTION_ARG(dst, argc); + + const GDScript *gds = _script; + + const Map<StringName, GDScriptFunction *>::Element *E = nullptr; + while (gds->base.ptr()) { + gds = gds->base.ptr(); + E = gds->member_functions.find(*methodname); + if (E) { + break; + } + } + + Callable::CallError err; + + if (E) { + *dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err); + } else if (gds->native.ptr()) { + if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { + MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname); + if (!mb) { + err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } else { + *dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err); + } + } else { + err.error = Callable::CallError::CALL_OK; + } + } else { + if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { + err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } else { + err.error = Callable::CallError::CALL_OK; + } + } + + if (err.error != Callable::CallError::CALL_OK) { + String methodstr = *methodname; + err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); + + OPCODE_BREAK; + } + + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_AWAIT) { + CHECK_SPACE(2); + + // Do the oneshot connect. + GET_INSTRUCTION_ARG(argobj, 0); + + Signal sig; + bool is_signal = true; + + { + Variant result = *argobj; + + if (argobj->get_type() == Variant::OBJECT) { + bool was_freed = false; + Object *obj = argobj->get_validated_object_with_check(was_freed); + + if (was_freed) { + err_text = "Trying to await on a freed object."; + OPCODE_BREAK; + } + + // Is this even possible to be null at this point? + if (obj) { + if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { + static StringName completed = _scs_create("completed"); + result = Signal(obj, completed); + } + } + } + + if (result.get_type() != Variant::SIGNAL) { + ip += 4; // Skip OPCODE_AWAIT_RESUME and its data. + // The stack pointer should be the same, so we don't need to set a return value. + is_signal = false; + } else { + sig = result; + } + } + + if (is_signal) { + Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState); + gdfs->function = this; + + gdfs->state.stack.resize(alloca_size); + //copy variant stack + for (int i = 0; i < _stack_size; i++) { + memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); + } + gdfs->state.stack_size = _stack_size; + gdfs->state.self = self; + gdfs->state.alloca_size = alloca_size; + gdfs->state.ip = ip + 2; + gdfs->state.line = line; + gdfs->state.script = _script; + { + MutexLock lock(GDScriptLanguage::get_singleton()->lock); + _script->pending_func_states.add(&gdfs->scripts_list); + if (p_instance) { + gdfs->state.instance = p_instance; + p_instance->pending_func_states.add(&gdfs->instances_list); + } else { + gdfs->state.instance = nullptr; + } + } +#ifdef DEBUG_ENABLED + gdfs->state.function_name = name; + gdfs->state.script_path = _script->get_path(); +#endif + gdfs->state.defarg = defarg; + gdfs->function = this; + + retvalue = gdfs; + + Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT); + if (err != OK) { + err_text = "Error connecting to signal: " + sig.get_name() + " during await."; + OPCODE_BREAK; + } + +#ifdef DEBUG_ENABLED + exit_ok = true; + awaited = true; +#endif + OPCODE_BREAK; + } + } + DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available). + + OPCODE(OPCODE_AWAIT_RESUME) { + CHECK_SPACE(2); +#ifdef DEBUG_ENABLED + if (!p_state) { + err_text = ("Invalid Resume (bug?)"); + OPCODE_BREAK; + } +#endif + GET_INSTRUCTION_ARG(result, 0); + *result = p_state->result; + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP) { + CHECK_SPACE(2); + int to = _code_ptr[ip + 1]; + + GD_ERR_BREAK(to < 0 || to > _code_size); + ip = to; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP_IF) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(test, 0); + + bool result = test->booleanize(); + + if (result) { + int to = _code_ptr[ip + 2]; + GD_ERR_BREAK(to < 0 || to > _code_size); + ip = to; + } else { + ip += 3; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP_IF_NOT) { + CHECK_SPACE(3); + + GET_INSTRUCTION_ARG(test, 0); + + bool result = test->booleanize(); + + if (!result) { + int to = _code_ptr[ip + 2]; + GD_ERR_BREAK(to < 0 || to > _code_size); + ip = to; + } else { + ip += 3; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) { + CHECK_SPACE(2); + ip = _default_arg_ptr[defarg]; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_RETURN) { + CHECK_SPACE(2); + GET_INSTRUCTION_ARG(r, 0); + retvalue = *r; +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif + OPCODE_BREAK; + } + + OPCODE(OPCODE_ITERATE_BEGIN) { + CHECK_SPACE(8); // Space for this and a regular iterate. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + bool valid; + if (!container->iter_init(*counter, valid)) { +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'."; + OPCODE_BREAK; + } +#endif + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + + *iterator = container->iter_get(*counter, valid); +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'."; + OPCODE_BREAK; + } +#endif + ip += 5; // Skip regular iterate which is always next. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_INT) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + int64_t size = *VariantInternal::get_int(container); + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = 0; + + if (size > 0) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::INT); + *VariantInternal::get_int(iterator) = 0; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_FLOAT) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + double size = *VariantInternal::get_float(container); + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_float(counter) = 0.0; + + if (size > 0) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::FLOAT); + *VariantInternal::get_float(iterator) = 0; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector2 *bounds = VariantInternal::get_vector2(container); + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_float(counter) = bounds->x; + + if (bounds->x < bounds->y) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::FLOAT); + *VariantInternal::get_float(iterator) = bounds->x; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2I) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector2i *bounds = VariantInternal::get_vector2i(container); + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_int(counter) = bounds->x; + + if (bounds->x < bounds->y) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::INT); + *VariantInternal::get_int(iterator) = bounds->x; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector3 *bounds = VariantInternal::get_vector3(container); + double from = bounds->x; + double to = bounds->y; + double step = bounds->z; + + VariantInternal::initialize(counter, Variant::FLOAT); + *VariantInternal::get_float(counter) = from; + + bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0); + + if (do_continue) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::FLOAT); + *VariantInternal::get_float(iterator) = from; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3I) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Vector3i *bounds = VariantInternal::get_vector3i(container); + int64_t from = bounds->x; + int64_t to = bounds->y; + int64_t step = bounds->z; + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = from; + + bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0); + + if (do_continue) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::INT); + *VariantInternal::get_int(iterator) = from; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_STRING) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + String *str = VariantInternal::get_string(container); + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = 0; + + if (!str->is_empty()) { + GET_INSTRUCTION_ARG(iterator, 2); + VariantInternal::initialize(iterator, Variant::STRING); + *VariantInternal::get_string(iterator) = str->substr(0, 1); + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_DICTIONARY) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Dictionary *dict = VariantInternal::get_dictionary(container); + const Variant *next = dict->next(nullptr); + + if (!dict->is_empty()) { + GET_INSTRUCTION_ARG(iterator, 2); + *counter = *next; + *iterator = *next; + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_BEGIN_ARRAY) { + CHECK_SPACE(8); // Check space for iterate instruction too. + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + Array *array = VariantInternal::get_array(container); + + VariantInternal::initialize(counter, Variant::INT); + *VariantInternal::get_int(counter) = 0; + + if (!array->is_empty()) { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = array->get(0); + + // Skip regular iterate. + ip += 5; + } else { + // Jump to end of loop. + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } + } + DISPATCH_OPCODE; + +#define OPCODE_ITERATE_BEGIN_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_var_ret_type, m_ret_type, m_ret_get_func) \ + OPCODE(OPCODE_ITERATE_BEGIN_PACKED_##m_var_type##_ARRAY) { \ + CHECK_SPACE(8); \ + GET_INSTRUCTION_ARG(counter, 0); \ + GET_INSTRUCTION_ARG(container, 1); \ + Vector<m_elem_type> *array = VariantInternal::m_get_func(container); \ + VariantInternal::initialize(counter, Variant::INT); \ + *VariantInternal::get_int(counter) = 0; \ + if (!array->is_empty()) { \ + GET_INSTRUCTION_ARG(iterator, 2); \ + VariantInternal::initialize(iterator, Variant::m_var_ret_type); \ + m_ret_type *it = VariantInternal::m_ret_get_func(iterator); \ + *it = array->get(0); \ + ip += 5; \ + } else { \ + int jumpto = _code_ptr[ip + 4]; \ + GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \ + ip = jumpto; \ + } \ + } \ + DISPATCH_OPCODE + + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, INT, int64_t, get_int); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT32, int32_t, get_int32_array, INT, int64_t, get_int); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(INT64, int64_t, get_int64_array, INT, int64_t, get_int); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT32, float, get_float32_array, FLOAT, double, get_float); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(FLOAT64, double, get_float64_array, FLOAT, double, get_float); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(STRING, String, get_string_array, STRING, String, get_string); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, VECTOR2, Vector2, get_vector2); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, VECTOR3, Vector3, get_vector3); + OPCODE_ITERATE_BEGIN_PACKED_ARRAY(COLOR, Color, get_color_array, COLOR, Color, get_color); + + OPCODE(OPCODE_ITERATE_BEGIN_OBJECT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *obj = container->get_validated_object_with_check(freed); + if (freed) { + err_text = "Trying to iterate on a previously freed object."; + OPCODE_BREAK; + } else if (!obj) { + err_text = "Trying to iterate on a null value."; + OPCODE_BREAK; + } +#else + Object *obj = *VariantInternal::get_object(container); +#endif + Array ref; + ref.push_back(*counter); + Variant vref; + VariantInternal::initialize(&vref, Variant::ARRAY); + *VariantInternal::get_array(&vref) = ref; + + Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore. + args[0] = &vref; + + Callable::CallError ce; + Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_init, (const Variant **)args, 1, ce); + +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + if (!has_next.booleanize()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce); +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + bool valid; + if (!container->iter_next(*counter, valid)) { +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; + OPCODE_BREAK; + } +#endif + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + + *iterator = container->iter_get(*counter, valid); +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; + OPCODE_BREAK; + } +#endif + ip += 5; //loop again + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_INT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + int64_t size = *VariantInternal::get_int(container); + int64_t *count = VariantInternal::get_int(counter); + + (*count)++; + + if (*count >= size) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_int(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_FLOAT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + double size = *VariantInternal::get_float(container); + double *count = VariantInternal::get_float(counter); + + (*count)++; + + if (*count >= size) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_float(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR2) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector2 *bounds = VariantInternal::get_vector2((const Variant *)container); + double *count = VariantInternal::get_float(counter); + + (*count)++; + + if (*count >= bounds->y) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_float(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR2I) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector2i *bounds = VariantInternal::get_vector2i((const Variant *)container); + int64_t *count = VariantInternal::get_int(counter); + + (*count)++; + + if (*count >= bounds->y) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_int(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR3) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector3 *bounds = VariantInternal::get_vector3((const Variant *)container); + double *count = VariantInternal::get_float(counter); + + *count += bounds->z; + + if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_float(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_VECTOR3I) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Vector3i *bounds = VariantInternal::get_vector3i((const Variant *)container); + int64_t *count = VariantInternal::get_int(counter); + + *count += bounds->z; + + if ((bounds->z < 0 && *count <= bounds->y) || (bounds->z > 0 && *count >= bounds->y)) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_int(iterator) = *count; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_STRING) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const String *str = VariantInternal::get_string((const Variant *)container); + int64_t *idx = VariantInternal::get_int(counter); + (*idx)++; + + if (*idx >= str->length()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *VariantInternal::get_string(iterator) = str->substr(*idx, 1); + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_DICTIONARY) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Dictionary *dict = VariantInternal::get_dictionary((const Variant *)container); + const Variant *next = dict->next(counter); + + if (!next) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *counter = *next; + *iterator = *next; + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ITERATE_ARRAY) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + + const Array *array = VariantInternal::get_array((const Variant *)container); + int64_t *idx = VariantInternal::get_int(counter); + (*idx)++; + + if (*idx >= array->size()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = array->get(*idx); + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + +#define OPCODE_ITERATE_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_ret_get_func) \ + OPCODE(OPCODE_ITERATE_PACKED_##m_var_type##_ARRAY) { \ + CHECK_SPACE(4); \ + GET_INSTRUCTION_ARG(counter, 0); \ + GET_INSTRUCTION_ARG(container, 1); \ + const Vector<m_elem_type> *array = VariantInternal::m_get_func((const Variant *)container); \ + int64_t *idx = VariantInternal::get_int(counter); \ + (*idx)++; \ + if (*idx >= array->size()) { \ + int jumpto = _code_ptr[ip + 4]; \ + GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \ + ip = jumpto; \ + } else { \ + GET_INSTRUCTION_ARG(iterator, 2); \ + *VariantInternal::m_ret_get_func(iterator) = array->get(*idx); \ + ip += 5; \ + } \ + } \ + DISPATCH_OPCODE + + OPCODE_ITERATE_PACKED_ARRAY(BYTE, uint8_t, get_byte_array, get_int); + OPCODE_ITERATE_PACKED_ARRAY(INT32, int32_t, get_int32_array, get_int); + OPCODE_ITERATE_PACKED_ARRAY(INT64, int64_t, get_int64_array, get_int); + OPCODE_ITERATE_PACKED_ARRAY(FLOAT32, float, get_float32_array, get_float); + OPCODE_ITERATE_PACKED_ARRAY(FLOAT64, double, get_float64_array, get_float); + OPCODE_ITERATE_PACKED_ARRAY(STRING, String, get_string_array, get_string); + OPCODE_ITERATE_PACKED_ARRAY(VECTOR2, Vector2, get_vector2_array, get_vector2); + OPCODE_ITERATE_PACKED_ARRAY(VECTOR3, Vector3, get_vector3_array, get_vector3); + OPCODE_ITERATE_PACKED_ARRAY(COLOR, Color, get_color_array, get_color); + + OPCODE(OPCODE_ITERATE_OBJECT) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(counter, 0); + GET_INSTRUCTION_ARG(container, 1); + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *obj = container->get_validated_object_with_check(freed); + if (freed) { + err_text = "Trying to iterate on a previously freed object."; + OPCODE_BREAK; + } else if (!obj) { + err_text = "Trying to iterate on a null value."; + OPCODE_BREAK; + } +#else + Object *obj = *VariantInternal::get_object(container); +#endif + Array ref; + ref.push_back(*counter); + Variant vref; + VariantInternal::initialize(&vref, Variant::ARRAY); + *VariantInternal::get_array(&vref) = ref; + + Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore. + args[0] = &vref; + + Callable::CallError ce; + Variant has_next = obj->call(CoreStringNames::get_singleton()->_iter_next, (const Variant **)args, 1, ce); + +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + if (!has_next.booleanize()) { + int jumpto = _code_ptr[ip + 4]; + GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); + ip = jumpto; + } else { + GET_INSTRUCTION_ARG(iterator, 2); + *iterator = obj->call(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce); +#ifdef DEBUG_ENABLED + if (ce.error != Callable::CallError::CALL_OK) { + err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container); + OPCODE_BREAK; + } +#endif + + ip += 5; // Loop again. + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_ASSERT) { + CHECK_SPACE(3); + +#ifdef DEBUG_ENABLED + GET_INSTRUCTION_ARG(test, 0); + bool result = test->booleanize(); + + if (!result) { + String message_str; + if (_code_ptr[ip + 2] != 0) { + GET_INSTRUCTION_ARG(message, 1); + message_str = *message; + } + if (message_str.is_empty()) { + err_text = "Assertion failed."; + } else { + err_text = "Assertion failed: " + message_str; + } + OPCODE_BREAK; + } + +#endif + ip += 3; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_BREAKPOINT) { +#ifdef DEBUG_ENABLED + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true); + } +#endif + ip += 1; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_LINE) { + CHECK_SPACE(2); + + line = _code_ptr[ip + 1]; + ip += 2; + + if (EngineDebugger::is_active()) { + // line + bool do_break = false; + + if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { + if (EngineDebugger::get_script_debugger()->get_depth() <= 0) { + EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1); + } + if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) { + do_break = true; + } + } + + if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) { + do_break = true; + } + + if (do_break) { + GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); + } + + EngineDebugger::get_singleton()->line_poll(); + } + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_END) { +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif + OPCODE_BREAK; + } + +#if 0 // Enable for debugging. + default: { + err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip); + OPCODE_BREAK; + } +#endif + } + + OPCODES_END +#ifdef DEBUG_ENABLED + if (exit_ok) { + OPCODE_OUT; + } + //error + // function, file, line, error, explanation + String err_file; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") { + err_file = p_instance->script->path; + } else if (script) { + err_file = script->path; + } + if (err_file == "") { + err_file = "<built-in>"; + } + String err_func = name; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->name != "") { + err_func = p_instance->script->name + "." + err_func; + } + int err_line = line; + if (err_text == "") { + err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please)."; + } + + if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) { + // debugger break did not happen + + _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT); + } + +#endif + OPCODE_OUT; + } + + OPCODES_OUT +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; + profile.total_time += time_taken; + profile.self_time += time_taken - function_call_time; + profile.frame_total_time += time_taken; + profile.frame_self_time += time_taken - function_call_time; + GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; + } + + // Check if this is the last time the function is resuming from await + // Will be true if never awaited as well + // When it's the last resume it will postpone the exit from stack, + // so the debugger knows which function triggered the resume of the next function (if any) + if (!p_state || awaited) { + if (EngineDebugger::is_active()) { + GDScriptLanguage::get_singleton()->exit_function(); + } +#endif + + if (_stack_size) { + //free stack + for (int i = 0; i < _stack_size; i++) { + stack[i].~Variant(); + } + } + +#ifdef DEBUG_ENABLED + } +#endif + + return retvalue; +} diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp new file mode 100644 index 0000000000..ad41b60a4e --- /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-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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/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..4b295b5eb8 --- /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-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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/string/ustring.h" +#include "core/templates/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/icons/GDScript.svg b/modules/gdscript/icons/GDScript.svg index 953bb9ae9e..aa59125ea9 100644 --- a/modules/gdscript/icons/GDScript.svg +++ b/modules/gdscript/icons/GDScript.svg @@ -1,5 +1 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<path transform="translate(0 1036.4)" d="m7 1l-0.56445 2.2578a5 5 0 0 0 -0.68945 0.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -0.28516 0.68555l-2.2539 0.5625v2l2.2578 0.56445a5 5 0 0 0 0.2793 0.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 0.68555 0.28516l0.5625 2.2539h2l0.56445-2.2578a5 5 0 0 0 0.6875 -0.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 0.28516 -0.68555l2.2539-0.5625v-2l-2.2578-0.56445a5 5 0 0 0 -0.2793 -0.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -0.68555 -0.28516l-0.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2z" fill="#e0e0e0"/> -</g> -</svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2l2.2578.56445a5 5 0 0 0 .2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 .68555.28516l.5625 2.2539h2l.56445-2.2578a5 5 0 0 0 .6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 .28516-.68555l2.2539-.5625v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0"/></svg> diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index b2c6b0e1ab..e63b6ab20e 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -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.is_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().is_empty()) { + value_text = "preload(\"" + res->get_path() + "\")"; + if (symbol.documentation.is_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.is_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.enum_value.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,12 +449,11 @@ 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); String last_part = line.substr(p_cursor.character + 1, lines[i].length()); - if (!p_symbol.empty()) { + if (!p_symbol.is_empty()) { String left_cursor_text; for (int c = p_cursor.character - 1; c >= 0; c--) { left_cursor_text = line.substr(c, p_cursor.character - c); @@ -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(), ""); @@ -477,7 +491,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int start_pos = p_position.character; for (int c = p_position.character; c >= 0; c--) { start_pos = c; - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -486,7 +500,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -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; @@ -539,7 +552,7 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi } while (c >= 0) { - const CharType &character = line[c]; + const char32_t &character = line[c]; if (character == ')') { ++bracket_stack; } else if (character == '(') { @@ -576,8 +589,7 @@ 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()) { + if (p_subclass.is_empty()) { const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); if (ptr) { return *ptr; @@ -599,12 +611,9 @@ const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const } const Array &ExtendGDScriptParser::get_member_completions() { - - if (member_completions.empty()) { - + if (member_completions.is_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(); j++) { + enum_dict[m.m_enum->values[j].identifier->name] = m.m_enum->values[j].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..28b9b3c82a 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -32,7 +32,7 @@ #define GDSCRIPT_EXTEND_PARSER_H #include "../gdscript_parser.h" -#include "core/variant.h" +#include "core/variant/variant.h" #include "lsp.hpp" #ifndef LINE_NUMBER_TO_INDEX @@ -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..5e3d6213d3 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -29,9 +29,11 @@ /*************************************************************************/ #include "gdscript_language_protocol.h" + +#include "core/config/project_settings.h" #include "core/io/json.h" #include "core/os/copymem.h" -#include "core/project_settings.h" +#include "editor/doc_tools.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -47,10 +49,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 +78,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++; } @@ -92,7 +96,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { // Response String output = GDScriptLanguageProtocol::get_singleton()->process_message(msg); - if (!output.empty()) { + if (!output.is_empty()) { res_queue.push_back(output.utf8()); } } @@ -101,7 +105,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { Error GDScriptLanguageProtocol::LSPeer::send_data() { int sent = 0; - if (!res_queue.empty()) { + if (!res_queue.is_empty()) { CharString c_res = res_queue[0]; if (res_sent < c_res.size()) { Error err = connection->put_partial_data((const uint8_t *)c_res.get_data() + res_sent, c_res.size() - res_sent - 1, sent); @@ -137,7 +141,7 @@ void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) { String GDScriptLanguageProtocol::process_message(const String &p_text) { String ret = process_string(p_text); - if (ret.empty()) { + if (ret.is_empty()) { return ret; } else { return format_output(ret); @@ -145,7 +149,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 +163,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 +171,6 @@ void GDScriptLanguageProtocol::_bind_methods() { } Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { - lsp::InitializeResult ret; String root_uri = p_params["rootUri"]; @@ -183,13 +185,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 +211,10 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { } void GDScriptLanguageProtocol::initialized(const Variant &p_params) { - lsp::GodotCapabilities capabilities; - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { - lsp::GodotNativeClassInfo gdclass; gdclass.name = E->get().name; gdclass.class_doc = &(E->get()); @@ -271,8 +272,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 +297,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..8b08ae0655 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -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..aac9cb7fd7 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -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_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 228d29bf42..218f42199e 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index f065b33570..9f2373bf56 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -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; @@ -152,13 +147,11 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { List<ScriptCodeCompletionOption> options; GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options); - if (!options.empty()) { - + if (!options.is_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]; @@ -270,7 +257,7 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { if ((item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) && !item.label.ends_with("):")) { item.insertText = item.label + "("; - if (symbol && symbol->children.empty()) { + if (symbol && symbol->children.is_empty()) { item.insertText += ")"; } } else if (item.kind == lsp::CompletionItemKind::Event) { @@ -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; @@ -357,7 +341,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) { params.load(p_params); List<const lsp::DocumentSymbol *> symbols; Array arr = this->find_symbols(params, symbols); - if (arr.empty() && !symbols.empty() && !symbols.front()->get()->native_class.empty()) { // Find a native symbol + if (arr.is_empty() && !symbols.is_empty() && !symbols.front()->get()->native_class.is_empty()) { // Find a native symbol const lsp::DocumentSymbol *symbol = symbols.front()->get(); if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { String id; @@ -441,7 +425,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(p_location, list); for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { if (const lsp::DocumentSymbol *s = E->get()) { - if (!s->uri.empty()) { + if (!s->uri.is_empty()) { lsp::Location location; location.uri = s->uri; location.range = s->range; diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index b2fd0c31f9..792e601bc1 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,8 +31,8 @@ #ifndef GDSCRIPT_TEXT_DOCUMENT_H #define GDSCRIPT_TEXT_DOCUMENT_H +#include "core/object/reference.h" #include "core/os/file_access.h" -#include "core/reference.h" #include "lsp.hpp" class GDScriptTextDocument : public Reference { diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 32fc8f36f0..7b502f079b 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -29,10 +29,12 @@ /*************************************************************************/ #include "gdscript_workspace.h" + #include "../gdscript.h" #include "../gdscript_parser.h" -#include "core/project_settings.h" -#include "core/script_language.h" +#include "core/config/project_settings.h" +#include "core/object/script_language.h" +#include "editor/doc_tools.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_node.h" @@ -41,12 +43,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 +73,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; @@ -79,7 +80,7 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_ if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) { const lsp::DocumentSymbol &class_symbol = E->value(); - if (p_member.empty()) { + if (p_member.is_empty()) { return &class_symbol; } else { for (int i = 0; i < class_symbol.children.size(); i++) { @@ -118,7 +119,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); } @@ -170,7 +171,7 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) Array GDScriptWorkspace::symbol(const Dictionary &p_params) { String query = p_params["query"]; Array arr; - if (!query.empty()) { + if (!query.is_empty()) { for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { Vector<lsp::DocumentedSymbolInformation> script_symbols; E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); @@ -185,11 +186,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(); + DocTools *doc = EditorHelp::get_doc_data(); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { - const DocData::ClassDoc &class_data = E->value(); lsp::DocumentSymbol class_symbol; String class_name = E->key(); @@ -197,7 +199,7 @@ Error GDScriptWorkspace::initialize() { class_symbol.native_class = class_name; class_symbol.kind = lsp::SymbolKind::Class; class_symbol.detail = String("<Native> class ") + class_name; - if (!class_data.inherits.empty()) { + if (!class_data.inherits.is_empty()) { class_symbol.detail += " extends " + class_data.inherits; } class_symbol.documentation = class_data.brief_description + "\n" + class_data.description; @@ -261,7 +263,7 @@ Error GDScriptWorkspace::initialize() { symbol_arg.kind = lsp::SymbolKind::Variable; symbol_arg.detail = arg.type; - if (!arg_default_value_started && !arg.default_value.empty()) { + if (!arg_default_value_started && !arg.default_value.is_empty()) { arg_default_value_started = true; } String arg_str = arg.name + ": " + arg.type; @@ -276,11 +278,11 @@ Error GDScriptWorkspace::initialize() { symbol.children.push_back(symbol_arg); } if (data.qualifiers.find("vararg") != -1) { - params += params.empty() ? "..." : ", ..."; + params += params.is_empty() ? "..." : ", ..."; } String return_type = data.return_type; - if (return_type.empty()) { + if (return_type.is_empty()) { return_type = "void"; } symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type; @@ -314,14 +316,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 +377,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 +394,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 +422,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 +437,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()) { @@ -450,26 +448,21 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } lsp::Position pos = p_doc_pos.position; - if (symbol_identifier.empty()) { + if (symbol_identifier.is_empty()) { Vector2i offset; symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); pos.character += offset.y; } - if (!symbol_identifier.empty()) { - + if (!symbol_identifier.is_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,9 +473,8 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } } else { - String member = ret.class_member; - if (member.empty() && symbol_identifier != ret.class_name) { + if (member.is_empty() && symbol_identifier != ret.class_name) { member = symbol_identifier; } symbol = get_native_symbol(ret.class_name, member); @@ -498,10 +490,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 +515,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,10 +527,9 @@ 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) { + if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) { return &symbol; } @@ -574,12 +562,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 +577,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/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index e45b06747d..7fd8bfcf20 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -32,7 +32,7 @@ #define GDSCRIPT_WORKSPACE_H #include "../gdscript_parser.h" -#include "core/variant.h" +#include "core/variant/variant.h" #include "editor/editor_file_system.h" #include "gdscript_extend_parser.h" #include "lsp.hpp" diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 124fcbfed8..6a913edbbf 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,9 +31,9 @@ #ifndef GODOT_LSP_H #define GODOT_LSP_H -#include "core/class_db.h" -#include "core/list.h" -#include "editor/doc_data.h" +#include "core/doc_data.h" +#include "core/object/class_db.h" +#include "core/templates/list.h" namespace lsp { @@ -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; } }; @@ -687,7 +687,7 @@ struct Diagnostic { dict["severity"] = severity; dict["message"] = message; dict["source"] = source; - if (!relatedInformation.empty()) { + if (!relatedInformation.is_empty()) { Array arr; arr.resize(relatedInformation.size()); for (int i = 0; i < relatedInformation.size(); i++) { @@ -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. @@ -1172,7 +1191,7 @@ struct DocumentSymbol { void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { DocumentedSymbolInformation si; - if (p_join_name && !p_container.empty()) { + if (p_join_name && !p_container.is_empty()) { si.name = p_container + ">" + name; } else { si.name = name; @@ -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"); @@ -1770,7 +1781,6 @@ static String marked_documentation(const String &p_bbcode) { } return markdown; } - } // namespace lsp #endif diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 62b9d94d6c..e90475a60e 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -35,31 +35,42 @@ #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" +#include "gdscript_utility_functions.h" + +#ifdef TESTS_ENABLED +#include "tests/test_gdscript.h" +#include "tests/test_macros.h" +#endif GDScriptLanguage *script_language_gd = nullptr; Ref<ResourceFormatLoaderGDScript> resource_loader_gd; Ref<ResourceFormatSaverGDScript> resource_saver_gd; +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 "core/config/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 +81,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 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,9 +112,7 @@ static void _editor_init() { #endif // TOOLS_ENABLED void register_gdscript_types() { - ClassDB::register_class<GDScript>(); - ClassDB::register_virtual_class<GDScriptFunctionState>(); script_language_gd = memnew(GDScriptLanguage); ScriptServer::register_language(script_language_gd); @@ -164,22 +123,64 @@ 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 + + GDScriptUtilityFunctions::register_functions(); } 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(); + GDScriptUtilityFunctions::unregister_functions(); +} + +#ifdef TESTS_ENABLED +void test_tokenizer() { + TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER); +} + +void test_parser() { + TestGDScript::test(TestGDScript::TestType::TEST_PARSER); +} + +void test_compiler() { + TestGDScript::test(TestGDScript::TestType::TEST_COMPILER); +} + +void test_bytecode() { + TestGDScript::test(TestGDScript::TestType::TEST_BYTECODE); } + +REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer); +REGISTER_TEST_COMMAND("gdscript-parser", &test_parser); +REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler); +REGISTER_TEST_COMMAND("gdscript-bytecode", &test_bytecode); +#endif diff --git a/modules/gdscript/register_types.h b/modules/gdscript/register_types.h index 18e57c1211..ce1c03d1d0 100644 --- a/modules/gdscript/register_types.h +++ b/modules/gdscript/register_types.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp new file mode 100644 index 0000000000..898ac653f5 --- /dev/null +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -0,0 +1,306 @@ +/*************************************************************************/ +/* test_gdscript.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "test_gdscript.h" + +#include "core/config/project_settings.h" +#include "core/io/file_access_pack.h" +#include "core/os/file_access.h" +#include "core/os/main_loop.h" +#include "core/os/os.h" +#include "core/string/string_builder.h" +#include "scene/resources/packed_scene.h" + +#include "modules/gdscript/gdscript_analyzer.h" +#include "modules/gdscript/gdscript_compiler.h" +#include "modules/gdscript/gdscript_parser.h" +#include "modules/gdscript/gdscript_tokenizer.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + +namespace TestGDScript { + +static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) { + GDScriptTokenizer tokenizer; + tokenizer.set_source_code(p_code); + + int tab_size = 4; +#ifdef TOOLS_ENABLED + if (EditorSettings::get_singleton()) { + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + } +#endif // TOOLS_ENABLED + String tab = String(" ").repeat(tab_size); + + GDScriptTokenizer::Token current = tokenizer.scan(); + while (current.type != GDScriptTokenizer::Token::TK_EOF) { + StringBuilder token; + token += " --> "; // Padding for line number. + + for (int l = current.start_line; l <= current.end_line; l++) { + print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab)); + } + + { + // Print carets to point at the token. + StringBuilder pointer; + pointer += " "; // Padding for line number. + int rightmost_column = current.rightmost_column; + if (current.end_line > current.start_line) { + rightmost_column--; // Don't point to the newline as a column. + } + for (int col = 1; col < rightmost_column; col++) { + if (col < current.leftmost_column) { + pointer += " "; + } else { + pointer += "^"; + } + } + print_line(pointer.as_string()); + } + + token += current.get_name(); + + if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) { + token += "("; + token += Variant::get_type_name(current.literal.get_type()); + token += ") "; + token += current.literal; + } + + print_line(token.as_string()); + + print_line("-------------------------------------------------------"); + + current = tokenizer.scan(); + } + + print_line(current.get_name()); // Should be EOF +} + +static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) { + GDScriptParser parser; + Error err = parser.parse(p_code, p_script_path, false); + + if (err != OK) { + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const GDScriptParser::ParserError &error = E->get(); + print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); + } + } + + GDScriptParser::TreePrinter printer; + + printer.print_tree(parser); +} + +static void test_compiler(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) { + GDScriptParser parser; + Error err = parser.parse(p_code, p_script_path, false); + + if (err != OK) { + print_line("Error in parser:"); + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const GDScriptParser::ParserError &error = E->get(); + print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); + } + return; + } + + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + + if (err != OK) { + print_line("Error in analyzer:"); + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const GDScriptParser::ParserError &error = E->get(); + print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); + } + return; + } + + GDScriptCompiler compiler; + Ref<GDScript> script; + script.instance(); + script->set_path(p_script_path); + + err = compiler.compile(&parser, script.ptr(), false); + + if (err) { + print_line("Error in compiler:"); + print_line(vformat("%02d:%02d: %s", compiler.get_error_line(), compiler.get_error_column(), compiler.get_error())); + return; + } + + for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { + const GDScriptFunction *func = E->value(); + + String signature = "Disassembling " + func->get_name().operator String() + "("; + for (int i = 0; i < func->get_argument_count(); i++) { + if (i > 0) { + signature += ", "; + } + signature += func->get_argument_name(i); + } + print_line(signature + ")"); + + func->disassemble(p_lines); + print_line(""); + print_line(""); + } +} + +void init_autoloads() { + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + + // First pass, add the constants so they exist before any script is loaded. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (info.is_singleton) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, Variant()); + } + } + } + + // Second pass, load into global constants. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (!info.is_singleton) { + // Skip non-singletons since we don't have a scene tree here anyway. + continue; + } + + RES res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); + Node *n = nullptr; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instance(); + } else if (res->is_class("Script")) { + Ref<Script> script_res = res; + StringName ibt = script_res->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); + + Object *obj = ClassDB::instance(ibt); + + ERR_CONTINUE_MSG(obj == nullptr, + "Cannot instance script for autoload, expected 'Node' inheritance, got: " + + String(ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(script_res); + } + + ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + n->set_name(info.name); + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, n); + } + } +} + +void test(TestType p_type) { + List<String> cmdlargs = OS::get_singleton()->get_cmdline_args(); + + if (cmdlargs.is_empty()) { + return; + } + + String test = cmdlargs.back()->get(); + if (!test.ends_with(".gd")) { + print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test); + return; + } + + FileAccessRef fa = FileAccess::open(test, FileAccess::READ); + ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test); + + // Init PackedData since it's used by ProjectSettings. + PackedData *packed_data = memnew(PackedData); + + // Setup project settings since it's needed by the languages to get the global scripts. + // This also sets up the base resource path. + Error err = ProjectSettings::get_singleton()->setup(fa->get_path_absolute().get_base_dir(), String(), true); + if (err) { + print_line("Could not load project settings."); + // Keep going since some scripts still work without this. + } + + // Initialize the language for the test routine. + ScriptServer::init_languages(); + init_autoloads(); + + Vector<uint8_t> buf; + int flen = fa->get_len(); + buf.resize(fa->get_len() + 1); + fa->get_buffer(buf.ptrw(), flen); + buf.write[flen] = 0; + + String code; + code.parse_utf8((const char *)&buf[0]); + + Vector<String> lines; + int last = 0; + for (int i = 0; i <= code.length(); i++) { + if (code[i] == '\n' || code[i] == 0) { + lines.push_back(code.substr(last, i - last)); + last = i + 1; + } + } + + switch (p_type) { + case TEST_TOKENIZER: + test_tokenizer(code, lines); + break; + case TEST_PARSER: + test_parser(code, test, lines); + break; + case TEST_COMPILER: + test_compiler(code, test, lines); + break; + case TEST_BYTECODE: + print_line("Not implemented."); + } + + // Destroy stuff we set up earlier. + ScriptServer::finish_languages(); + memdelete(packed_data); +} +} // namespace TestGDScript diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h new file mode 100644 index 0000000000..bbda46cdad --- /dev/null +++ b/modules/gdscript/tests/test_gdscript.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* test_gdscript.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_GDSCRIPT_H +#define TEST_GDSCRIPT_H + +namespace TestGDScript { + +enum TestType { + TEST_TOKENIZER, + TEST_PARSER, + TEST_COMPILER, + TEST_BYTECODE, +}; + +void test(TestType p_type); +} // namespace TestGDScript + +#endif // TEST_GDSCRIPT_H |