diff options
Diffstat (limited to 'modules/gdscript')
81 files changed, 13286 insertions, 7781 deletions
diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index e58a1d8edc..5c8cbdf869 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -17,3 +17,7 @@ if env["tools"]: # Using a define in the disabled case, to avoid having an extra define # in regular builds where all modules are enabled. env_gdscript.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) + +if env["tests"]: + env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"]) + env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py index 6fc227e7f5..61ce6185a5 100644 --- a/modules/gdscript/config.py +++ b/modules/gdscript/config.py @@ -10,7 +10,6 @@ def get_doc_classes(): return [ "@GDScript", "GDScript", - "GDScriptFunctionState", ] diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 9e40a69712..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. positive value). - [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,86 +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 have both [code]y[/code] and [code]x[/code]. - [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. - Important note: The Y coordinate comes first, by convention. - [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 (towards positive infinity), returning the smallest whole number that is not less than [code]s[/code]. - [codeblock] - i = ceil(1.45) # i is 2 - i = ceil(1.001) # i is 2 - [/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). @@ -181,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"> @@ -225,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> - Converts an angle expressed in degrees 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> - <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. The curve values are: 0 is constant, 1 is linear, 0 to 1 is ease-in, 1+ is ease out. Negative values are in-out/out in. - </description> - </method> - <method name="exp"> - <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, and can be obtained with [code]exp(1)[/code]. - For exponents to other bases use the method [method pow]. - [codeblock] - a = exp(2) # Approximately 7.39 - [/codeblock] - </description> - </method> - <method name="floor"> - <return type="float"> - </return> - <argument index="0" name="s" type="float"> - </argument> - <description> - Rounds [code]s[/code] downward (towards negative infinity), returning the largest whole number that is not more than [code]s[/code]. - [codeblock] - # a is 2.0 - a = floor(2.99) - # a is -3.0 - a = floor(-2.99) - [/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 index="0" name="dictionary" type="Dictionary"> </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"> @@ -427,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). @@ -460,92 +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. - Here, approximately equal means that [code]a[/code] and [code]b[/code] are within a small internal epsilon of each other, which scales with the magnitude of the numbers. - Infinity values of the same sign are considered equal. - </description> - </method> - <method name="is_inf"> - <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" or invalid) 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. - This method is faster than using [method is_equal_approx] with one value as zero. - </description> - </method> <method name="len"> <return type="int"> </return> @@ -560,63 +151,6 @@ [/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> @@ -630,178 +164,7 @@ var main = load("res://main.tscn") # main will contain a PackedScene resource. [/codeblock] [b]Important:[/b] The path must be absolute, a local path will just return [code]null[/code]. - </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] - [b]Note:[/b] The logarithm of [code]0[/code] returns [code]-inf[/code], while negative values return [code]-nan[/code]. - </description> - </method> - <method name="max"> - <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(5, 10, 4) # Returns 9 - move_toward(10, 5, 4) # Returns 6 - move_toward(10, 5, -1.5) # Returns 11.5 - [/codeblock] - </description> - </method> - <method name="nearest_po2"> - <return type="int"> - </return> - <argument index="0" name="value" type="int"> - </argument> - <description> - Returns the nearest equal or larger power of 2 for integer [code]value[/code]. - In other words, returns the smallest value [code]a[/code] where [code]a = pow(2, n)[/code] such that [code]value <= a[/code] for some non-negative integer [code]n[/code]. - [codeblock] - nearest_po2(3) # Returns 4 - nearest_po2(4) # Returns 4 - nearest_po2(5) # Returns 8 - - nearest_po2(0) # Returns 0 (this may not be what you expect) - nearest_po2(-1) # Returns 0 (this may not be what you expect) - [/codeblock] - [b]WARNING:[/b] Due to the way it is implemented, this function returns [code]0[/code] rather than [code]1[/code] for non-positive values of [code]value[/code] (in reality, 1 is the smallest integer power of 2). - </description> - </method> - <method name="ord"> - <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 the Variant's type is what you expect.) - [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, parsing a JSON text will convert all numerical values to [float] types. - [b]Note:[/b] JSON objects do not preserve key order like Godot dictionaries, thus, you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements: - [codeblock] - var p = JSON.parse('["hello", "world", "!"]') - if typeof(p.result) == TYPE_ARRAY: - print(p.result[0]) # Prints "hello" - else: - push_error("Unexpected results.") - [/codeblock] - See also [JSON] for an alternative way to parse JSON text. - </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"> @@ -818,22 +181,11 @@ [/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] - [/codeblock] - </description> - </method> <method name="print_debug" qualifiers="vararg"> <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"> @@ -847,319 +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 an angle expressed in radians to degrees. - [codeblock] - rad2deg(0.523599) # Returns 30 - [/codeblock] - </description> - </method> - <method name="rand_range"> - <return type="float"> - </return> - <argument index="0" name="from" type="float"> - </argument> - <argument index="1" name="to" type="float"> - </argument> - <description> - Random range, any floating point value between [code]from[/code] and [code]to[/code]. - [codeblock] - prints(rand_range(0, 1), rand_range(0, 1)) # Prints e.g. 0.135591 0.405263 - [/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> - Rounds [code]s[/code] to the nearest whole number, 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="s" type="float"> - </argument> - <description> - Returns the result of smoothly interpolating the value of [code]s[/code] between [code]0[/code] and [code]1[/code], based on the where [code]s[/code] lies with respect to the edges [code]from[/code] and [code]to[/code]. - The return value is [code]0[/code] if [code]s <= from[/code], and [code]1[/code] if [code]s >= to[/code]. If [code]s[/code] lies between [code]from[/code] and [code]to[/code], the returned value follows an S-shaped curve that maps [code]s[/code] between [code]0[/code] and [code]1[/code]. - This S-shaped curve is the cubic Hermite interpolator, given by [code]f(s) = 3*s^2 - 2*s^3[/code]. - [codeblock] - smoothstep(0, 2, -5.0) # Returns 0.0 - smoothstep(0, 2, 0.5) # Returns 0.15625 - smoothstep(0, 2, 1.0) # Returns 0.5 - smoothstep(0, 2, 2.0) # Returns 1.0 - [/codeblock] - </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], where [code]s[/code] is a non-negative number. - [codeblock] - sqrt(9) # Returns 3 - [/codeblock] - [b]Note:[/b]Negative values of [code]s[/code] return NaN. If you need negative inputs, use [code]System.Numerics.Complex[/code] in C#. - </description> - </method> - <method name="step_decimals"> - <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> @@ -1176,200 +230,12 @@ [/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] - # Both numbers below are integers. - a = { "a": 1, "b": 2 } - b = to_json(a) - print(b) # {"a":1, "b":2} - # Both numbers above are floats, even if they display without any decimal places. - [/codeblock] - [b]Note:[/b] The JSON specification does not define integer or float types, but only a [i]number[/i] type. Therefore, converting a [Variant] to JSON text will convert all numerical values to [float] types. - See also [JSON] for an alternative way to convert a [Variant] to JSON text. - </description> - </method> <method name="type_exists"> <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 index="0" name="type" type="StringName"> </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 JSON.") - else: - push_error("Invalid JSON: " + 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> - <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] - # Infinite loop between 5.0 and 9.9 - value = wrapf(value + 0.1, 5.0, 10.0) - [/codeblock] - [codeblock] - # Infinite rotation (in radians) - angle = wrapf(angle + 0.1, 0.0, TAU) - [/codeblock] - [codeblock] - # Infinite rotation (in radians) - angle = wrapf(angle + 0.1, -PI, PI) - [/codeblock] - [b]Note:[/b] If [code]min[/code] is [code]0[/code], this is equivalent to [method fposmod], so prefer using that instead. - [code]wrapf[/code] is more flexible than using the [method fposmod] approach by giving the user control over the minimum value. - </description> - </method> - <method name="wrapi"> - <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] - # Infinite loop between 5 and 9 - frame = wrapi(frame + 1, 5, 10) - [/codeblock] - [codeblock] - # result is -2 - var result = wrapi(-6, -5, -1) - [/codeblock] - [b]Note:[/b] If [code]min[/code] is [code]0[/code], this is equivalent to [method posmod], so prefer using that instead. - [code]wrapi[/code] is more flexible than using the [method posmod] approach by giving the user control over the minimum value. </description> </method> </methods> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 62ccb93901..631a102130 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -8,7 +8,7 @@ [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - <link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> + <link title="GDScript tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> </tutorials> <methods> <method name="get_as_byte_code" qualifiers="const"> diff --git a/modules/gdscript/doc_classes/GDScriptFunctionState.xml b/modules/gdscript/doc_classes/GDScriptFunctionState.xml deleted file mode 100644 index 5e369b32d9..0000000000 --- a/modules/gdscript/doc_classes/GDScriptFunctionState.xml +++ /dev/null @@ -1,45 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="GDScriptFunctionState" inherits="Reference" version="4.0"> - <brief_description> - State of a function call after yielding. - </brief_description> - <description> - FIXME: Outdated docs as of GDScript rewrite in 4.0. - Calling [code]yield[/code] within a function will cause that function to yield and return its current state as an object of this type. The yielded function call can then be resumed later by calling [method resume] on this state object. - </description> - <tutorials> - </tutorials> - <methods> - <method name="is_valid" qualifiers="const"> - <return type="bool"> - </return> - <argument index="0" name="extended_check" type="bool" default="false"> - </argument> - <description> - Check whether the function call may be resumed. This is not the case if the function state was already resumed. - If [code]extended_check[/code] is enabled, it also checks if the associated script and object still exist. The extended check is done in debug mode as part of [method GDScriptFunctionState.resume], but you can use this if you know you may be trying to resume without knowing for sure the object and/or script have survived up to that point. - </description> - </method> - <method name="resume"> - <return type="Variant"> - </return> - <argument index="0" name="arg" type="Variant" default="null"> - </argument> - <description> - Resume execution of the yielded function call. - If handed an argument, return the argument from the [code]yield[/code] call in the yielded function call. You can pass e.g. an [Array] to hand multiple arguments. - This function returns what the resumed function call returns, possibly another function state if yielded again. - </description> - </method> - </methods> - <signals> - <signal name="completed"> - <argument index="0" name="result" type="Variant"> - </argument> - <description> - </description> - </signal> - </signals> - <constants> - </constants> -</class> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index ae1f2893f1..ca646dff15 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 */ @@ -33,15 +33,15 @@ #include "../gdscript_tokenizer.h" #include "editor/editor_settings.h" -static bool _is_char(CharType c) { +static bool _is_char(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -static bool _is_hex_symbol(CharType c) { +static bool _is_hex_symbol(char32_t c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } -static bool _is_bin_symbol(CharType c) { +static bool _is_bin_symbol(char32_t c) { return (c == '0' || c == '1'); } @@ -73,6 +73,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) color_region_cache[p_line] = -1; int in_region = -1; if (p_line != 0) { + int prev_region_line = p_line - 1; + while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) { + prev_region_line--; + } + for (int i = prev_region_line; i < p_line - 1; i++) { + get_line_syntax_highlighting(i); + } if (!color_region_cache.has(p_line - 1)) { get_line_syntax_highlighting(p_line - 1); } @@ -119,7 +126,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) /* search the line */ bool match = true; - const CharType *start_key = color_regions[c].start_key.c_str(); + const char32_t *start_key = color_regions[c].start_key.get_data(); for (int k = 0; k < start_key_length; k++) { if (start_key[k] != str[from + k]) { match = false; @@ -153,18 +160,16 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) /* if we are in one find the end key */ if (in_region != -1) { - /* check there is enough room */ - int chars_left = line_length - from; - int end_key_length = color_regions[in_region].end_key.length(); - if (chars_left < end_key_length) { - continue; - } - /* search the line */ int region_end_index = -1; - const CharType *end_key = color_regions[in_region].start_key.c_str(); + int end_key_length = color_regions[in_region].end_key.length(); + const char32_t *end_key = color_regions[in_region].end_key.get_data(); for (; from < line_length; from++) { - if (!is_a_symbol) { + if (line_length - from < end_key_length) { + break; + } + + if (!is_symbol(str[from])) { continue; } @@ -173,9 +178,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) continue; } + region_end_index = from; for (int k = 0; k < end_key_length; k++) { - if (end_key[k] == str[from + k]) { - region_end_index = from; + if (end_key[k] != str[from + k]) { + region_end_index = -1; break; } } @@ -192,7 +198,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) previous_type = REGION; previous_text = ""; previous_column = j; - j = from; + j = from + (end_key_length - 1); if (region_end_index == -1) { color_region_cache[p_line] = in_region; } @@ -263,19 +269,21 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) col = keywords[word]; } else if (member_keywords.has(word)) { col = member_keywords[word]; + } + + if (col != Color()) { for (int k = j - 1; k >= 0; k--) { if (str[k] == '.') { - col = Color(); //member indexing not allowed + col = Color(); // keyword & member indexing not allowed break; } else if (str[k] > 32) { break; } } - } - - if (col != Color()) { - in_keyword = true; - keyword_color = col; + if (col != Color()) { + in_keyword = true; + keyword_color = col; + } } } @@ -477,10 +485,15 @@ void GDScriptSyntaxHighlighter::_update_cache() { /* Reserved words. */ const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_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; + if (gdscript->is_control_flow_keyword(E->get())) { + keywords[E->get()] = control_flow_keyword_color; + } else { + keywords[E->get()] = keyword_color; + } } /* Comments */ @@ -572,8 +585,12 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons } } + int at = 0; for (int i = 0; i < color_regions.size(); i++) { ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists."); + if (p_start_key.length() < color_regions[i].start_key.length()) { + at++; + } } ColorRegion color_region; @@ -581,7 +598,8 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons color_region.start_key = p_start_key; color_region.end_key = p_end_key; color_region.line_only = p_line_only; - color_regions.push_back(color_region); + color_regions.insert(at, color_region); + clear_highlighting_cache(); } Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const { diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 49357f3d2e..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 */ @@ -42,7 +42,7 @@ private: Color color; String start_key; String end_key; - bool line_only; + bool line_only = false; }; Vector<ColorRegion> color_regions; Map<int, int> color_region_cache; diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 6d454e43f2..9d0d91162c 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.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 */ @@ -37,119 +37,315 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions); } -Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_extracted_strings) { - // Parse and match all GDScript function API that involves translation string. - // E.g get_node("Label").text = "something", var test = tr("something"), "something" will be matched and collected. +Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) { + // Extract all translatable strings using the parsed tree from GDSriptParser. + // The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e + // Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc. + // Search strings in AssignmentNode -> text = "__", hint_tooltip = "__" etc. Error err; - RES loaded_res = ResourceLoader::load(p_path, "", false, &err); + RES loaded_res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &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(); - Vector<String> parsed_strings; - - // Search translation strings with RegEx. - regex.clear(); - regex.compile(String("|").join(patterns)); - Array results = regex.search_all(source_code); - _get_captured_strings(results, &parsed_strings); - - // Special handling for FileDialog. - Vector<String> temp; - _parse_file_dialog(source_code, &temp); - parsed_strings.append_array(temp); - - // Filter out / and + - String filter = "(?:\\\\\\n|\"[\\s\\\\]*\\+\\s*\")"; - regex.clear(); - regex.compile(filter); - for (int i = 0; i < parsed_strings.size(); i++) { - parsed_strings.set(i, regex.sub(parsed_strings[i], "", true)); + + GDScriptParser parser; + err = parser.parse(source_code, p_path, false); + if (err != OK) { + ERR_PRINT("Failed to parse with GDScript with GDScriptParser."); + return err; } - r_extracted_strings->append_array(parsed_strings); + // Traverse through the parsed tree from GDScriptParser. + GDScriptParser::ClassNode *c = parser.get_tree(); + _traverse_class(c); return OK; } -void GDScriptEditorTranslationParserPlugin::_parse_file_dialog(const String &p_source_code, Vector<String> *r_output) { - // FileDialog API has the form .filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). - // First filter: Get "*.png ; PNG Images", "*.gd ; GDScript Files" from PackedStringArray. - regex.clear(); - regex.compile(String("|").join(file_dialog_patterns)); - Array results = regex.search_all(p_source_code); - - Vector<String> temp; - _get_captured_strings(results, &temp); - String captured_strings = String(",").join(temp); - - // Second filter: Get the texts after semicolon from "*.png ; PNG Images","*.gd ; GDScript Files". - String second_filter = "\"[^;]+;" + text + "\""; - regex.clear(); - regex.compile(second_filter); - results = regex.search_all(captured_strings); - _get_captured_strings(results, r_output); - for (int i = 0; i < r_output->size(); i++) { - r_output->set(i, r_output->get(i).strip_edges()); +void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) { + for (int i = 0; i < p_class->members.size(); i++) { + const GDScriptParser::ClassNode::Member &m = p_class->members[i]; + // There are 7 types of Member, but only class, function and variable can contain translatable strings. + switch (m.type) { + case GDScriptParser::ClassNode::Member::CLASS: + _traverse_class(m.m_class); + break; + case GDScriptParser::ClassNode::Member::FUNCTION: + _traverse_function(m.function); + break; + case GDScriptParser::ClassNode::Member::VARIABLE: + _read_variable(m.variable); + break; + default: + break; + } + } +} + +void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) { + _traverse_block(p_func->body); +} + +void GDScriptEditorTranslationParserPlugin::_read_variable(const GDScriptParser::VariableNode *p_var) { + _assess_expression(p_var->initializer); +} + +void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser::SuiteNode *p_suite) { + if (!p_suite) { + return; + } + + const Vector<GDScriptParser::Node *> &statements = p_suite->statements; + for (int i = 0; i < statements.size(); i++) { + GDScriptParser::Node *statement = statements[i]; + + // Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings. + switch (statement->type) { + case GDScriptParser::Node::VARIABLE: + _assess_expression(static_cast<GDScriptParser::VariableNode *>(statement)->initializer); + break; + case GDScriptParser::Node::IF: { + GDScriptParser::IfNode *if_node = static_cast<GDScriptParser::IfNode *>(statement); + _assess_expression(if_node->condition); + //FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if(). + _traverse_block(if_node->true_block); + _traverse_block(if_node->false_block); + break; + } + case GDScriptParser::Node::FOR: { + GDScriptParser::ForNode *for_node = static_cast<GDScriptParser::ForNode *>(statement); + _assess_expression(for_node->list); + _traverse_block(for_node->loop); + break; + } + case GDScriptParser::Node::WHILE: { + GDScriptParser::WhileNode *while_node = static_cast<GDScriptParser::WhileNode *>(statement); + _assess_expression(while_node->condition); + _traverse_block(while_node->loop); + break; + } + case GDScriptParser::Node::MATCH: { + GDScriptParser::MatchNode *match_node = static_cast<GDScriptParser::MatchNode *>(statement); + _assess_expression(match_node->test); + for (int j = 0; j < match_node->branches.size(); j++) { + _traverse_block(match_node->branches[j]->block); + } + break; + } + case GDScriptParser::Node::RETURN: + _assess_expression(static_cast<GDScriptParser::ReturnNode *>(statement)->return_value); + break; + case GDScriptParser::Node::ASSERT: + _assess_expression((static_cast<GDScriptParser::AssertNode *>(statement))->condition); + break; + case GDScriptParser::Node::ASSIGNMENT: + _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(statement)); + break; + default: + if (statement->is_expression()) { + _assess_expression(static_cast<GDScriptParser::ExpressionNode *>(statement)); + } + break; + } + } +} + +void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::ExpressionNode *p_expression) { + // Explore all ExpressionNodes to find CallNodes which contain translation strings, such as tr(), set_text() etc. + // tr() can be embedded quite deep within multiple ExpressionNodes so need to dig down to search through all ExpressionNodes. + if (!p_expression) { + return; + } + + // ExpressionNode of type await, cast, get_node, identifier, literal, preload, self, subscript, unary are ignored as they can't be CallNode + // containing translation strings. + switch (p_expression->type) { + case GDScriptParser::Node::ARRAY: { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(p_expression); + for (int i = 0; i < array_node->elements.size(); i++) { + _assess_expression(array_node->elements[i]); + } + break; + } + case GDScriptParser::Node::ASSIGNMENT: + _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression)); + break; + case GDScriptParser::Node::BINARY_OPERATOR: { + GDScriptParser::BinaryOpNode *binary_op_node = static_cast<GDScriptParser::BinaryOpNode *>(p_expression); + _assess_expression(binary_op_node->left_operand); + _assess_expression(binary_op_node->right_operand); + break; + } + case GDScriptParser::Node::CALL: { + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_expression); + _extract_from_call(call_node); + for (int i = 0; i < call_node->arguments.size(); i++) { + _assess_expression(call_node->arguments[i]); + } + } break; + case GDScriptParser::Node::DICTIONARY: { + GDScriptParser::DictionaryNode *dict_node = static_cast<GDScriptParser::DictionaryNode *>(p_expression); + for (int i = 0; i < dict_node->elements.size(); i++) { + _assess_expression(dict_node->elements[i].key); + _assess_expression(dict_node->elements[i].value); + } + break; + } + case GDScriptParser::Node::TERNARY_OPERATOR: { + GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<GDScriptParser::TernaryOpNode *>(p_expression); + _assess_expression(ternary_op_node->condition); + _assess_expression(ternary_op_node->true_expr); + _assess_expression(ternary_op_node->false_expr); + break; + } + default: + break; + } +} + +void GDScriptEditorTranslationParserPlugin::_assess_assignment(GDScriptParser::AssignmentNode *p_assignment) { + // Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____" + + StringName assignee_name; + if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + assignee_name = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name; + } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { + assignee_name = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name; + } + + if (assignment_patterns.has(assignee_name) && p_assignment->assigned_value->type == GDScriptParser::Node::LITERAL) { + // If the assignment is towards one of the extract patterns (text, hint_tooltip etc.), and the value is a string literal, we collect the string. + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_assignment->assigned_value)->value); + } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) { + // FileDialog.filters accepts assignment in the form of PackedStringArray. For example, + // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). + + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value); + if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]); + + // Extract the name in "extension ; name" of PackedStringArray. + for (int i = 0; i < array_node->elements.size(); i++) { + _extract_fd_literals(array_node->elements[i]); + } + } + } else { + // If the assignee is not in extract patterns or the assigned_value is not Literal type, try to see if the assigned_value contains tr(). + _assess_expression(p_assignment->assigned_value); } } -void GDScriptEditorTranslationParserPlugin::_get_captured_strings(const Array &p_results, Vector<String> *r_output) { - Ref<RegExMatch> result; - for (int i = 0; i < p_results.size(); i++) { - result = p_results[i]; - for (int j = 0; j < result->get_group_count(); j++) { - String s = result->get_string(j + 1); - // Prevent reading text with only spaces. - if (!s.strip_edges().empty()) { - r_output->push_back(s); +void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::CallNode *p_call) { + // Extract the translatable strings coming from function calls. For example: + // tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____"). + + StringName function_name = p_call->function_name; + + // Variables for extracting tr() and tr_n(). + Vector<String> id_ctx_plural; + id_ctx_plural.resize(3); + bool extract_id_ctx_plural = true; + + if (function_name == tr_func) { + // Extract from tr(id, ctx). + for (int i = 0; i < p_call->arguments.size(); i++) { + if (p_call->arguments[i]->type == GDScriptParser::Node::LITERAL) { + id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[i])->value; + } else { + // Avoid adding something like tr("Flying dragon", var_context_level_1). We want to extract both id and context together. + extract_id_ctx_plural = false; + } + } + if (extract_id_ctx_plural) { + ids_ctx_plural->push_back(id_ctx_plural); + } + } else if (function_name == trn_func) { + // Extract from tr_n(id, plural, n, ctx). + Vector<int> indices; + indices.push_back(0); + indices.push_back(3); + indices.push_back(1); + for (int i = 0; i < indices.size(); i++) { + if (indices[i] >= p_call->arguments.size()) { + continue; + } + + if (p_call->arguments[indices[i]]->type == GDScriptParser::Node::LITERAL) { + id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[indices[i]])->value; + } else { + extract_id_ctx_plural = false; + } + } + if (extract_id_ctx_plural) { + ids_ctx_plural->push_back(id_ctx_plural); + } + } else if (first_arg_patterns.has(function_name)) { + // Extracting argument with only string literals. In other words, not extracting something like set_text("hello " + some_var). + if (p_call->arguments[0]->type == GDScriptParser::Node::LITERAL) { + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[0])->value); + } + } else if (second_arg_patterns.has(function_name)) { + if (p_call->arguments[1]->type == GDScriptParser::Node::LITERAL) { + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[1])->value); + } + } else if (function_name == fd_add_filter) { + // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images"). + _extract_fd_literals(p_call->arguments[0]); + + } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) { + // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example, + // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])). + + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_call->arguments[0]); + if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]); + for (int i = 0; i < array_node->elements.size(); i++) { + _extract_fd_literals(array_node->elements[i]); } } } } +void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) { + // Extract the name in "extension ; name". + + if (p_expression->type == GDScriptParser::Node::LITERAL) { + String arg_val = String(static_cast<GDScriptParser::LiteralNode *>(p_expression)->value); + PackedStringArray arr = arg_val.split(";", true); + if (arr.size() != 2) { + ERR_PRINT("Argument for setting FileDialog has bad format."); + return; + } + ids->push_back(arr[1].strip_edges()); + } +} + GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() { - // Regex search pattern templates. - // The extra complication in the regex pattern is to ensure that the matching works when users write over multiple lines, use tabs etc. - const String dot = "\\.[\\s\\\\]*"; - const String str_assign_template = "[\\s\\\\]*=[\\s\\\\]*\"" + text + "\""; - const String first_arg_template = "[\\s\\\\]*\\([\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)"; - const String second_arg_template = "[\\s\\\\]*\\([\\s\\S]+?,[\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)"; - - // Common patterns. - patterns.push_back("tr" + first_arg_template); - patterns.push_back(dot + "text" + str_assign_template); - patterns.push_back(dot + "placeholder_text" + str_assign_template); - patterns.push_back(dot + "hint_tooltip" + str_assign_template); - patterns.push_back(dot + "set_text" + first_arg_template); - patterns.push_back(dot + "set_tooltip" + first_arg_template); - patterns.push_back(dot + "set_placeholder" + first_arg_template); - - // Tabs and TabContainer API. - patterns.push_back(dot + "set_tab_title" + second_arg_template); - patterns.push_back(dot + "add_tab" + first_arg_template); - - // PopupMenu API. - patterns.push_back(dot + "add_check_item" + first_arg_template); - patterns.push_back(dot + "add_icon_check_item" + second_arg_template); - patterns.push_back(dot + "add_icon_item" + second_arg_template); - patterns.push_back(dot + "add_icon_radio_check_item" + second_arg_template); - patterns.push_back(dot + "add_item" + first_arg_template); - patterns.push_back(dot + "add_multistate_item" + first_arg_template); - patterns.push_back(dot + "add_radio_check_item" + first_arg_template); - patterns.push_back(dot + "add_separator" + first_arg_template); - patterns.push_back(dot + "add_submenu_item" + first_arg_template); - patterns.push_back(dot + "set_item_text" + second_arg_template); - //patterns.push_back(dot + "set_item_tooltip" + second_arg_template); //no tr() behind this function. might be bug. - - // FileDialog API - special case. - const String fd_text = "((?:[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*\"[\\s\\\\]*,?)*)"; - const String packed_string_array = "[\\s\\\\]*PackedStringArray[\\s\\\\]*\\([\\s\\\\]*\\[" + fd_text + "\\][\\s\\\\]*\\)"; - file_dialog_patterns.push_back(dot + "add_filter[\\s\\\\]*\\(" + fd_text + "[\\s\\\\]*\\)"); - file_dialog_patterns.push_back(dot + "filters[\\s\\\\]*=" + packed_string_array); - file_dialog_patterns.push_back(dot + "set_filters[\\s\\\\]*\\(" + packed_string_array + "[\\s\\\\]*\\)"); + assignment_patterns.insert("text"); + assignment_patterns.insert("placeholder_text"); + assignment_patterns.insert("hint_tooltip"); + + first_arg_patterns.insert("set_text"); + first_arg_patterns.insert("set_tooltip"); + first_arg_patterns.insert("set_placeholder"); + first_arg_patterns.insert("add_tab"); + first_arg_patterns.insert("add_check_item"); + first_arg_patterns.insert("add_item"); + first_arg_patterns.insert("add_multistate_item"); + first_arg_patterns.insert("add_radio_check_item"); + first_arg_patterns.insert("add_separator"); + first_arg_patterns.insert("add_submenu_item"); + + second_arg_patterns.insert("set_tab_title"); + second_arg_patterns.insert("add_icon_check_item"); + second_arg_patterns.insert("add_icon_item"); + second_arg_patterns.insert("add_icon_radio_check_item"); + second_arg_patterns.insert("set_item_text"); } diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index 9fa4b69f01..fcf438422a 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.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,23 +31,40 @@ #ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H #define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H +#include "core/templates/set.h" #include "editor/editor_translation_parser.h" +#include "modules/gdscript/gdscript_parser.h" #include "modules/regex/regex.h" class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); - // Regex and search patterns that are used to match translation strings. - const String text = "((?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*)"; - RegEx regex; - Vector<String> patterns; - Vector<String> file_dialog_patterns; + Vector<String> *ids = nullptr; + Vector<Vector<String>> *ids_ctx_plural = nullptr; - void _parse_file_dialog(const String &p_source_code, Vector<String> *r_output); - void _get_captured_strings(const Array &p_results, Vector<String> *r_output); + // List of patterns used for extracting translation strings. + StringName tr_func = "tr"; + StringName trn_func = "tr_n"; + Set<StringName> assignment_patterns; + Set<StringName> first_arg_patterns; + Set<StringName> second_arg_patterns; + // FileDialog patterns. + StringName fd_add_filter = "add_filter"; + StringName fd_set_filter = "set_filters"; + StringName fd_filters = "filters"; + + void _traverse_class(const GDScriptParser::ClassNode *p_class); + void _traverse_function(const GDScriptParser::FunctionNode *p_func); + void _traverse_block(const GDScriptParser::SuiteNode *p_suite); + + void _read_variable(const GDScriptParser::VariableNode *p_var); + void _assess_expression(GDScriptParser::ExpressionNode *p_expression); + void _assess_assignment(GDScriptParser::AssignmentNode *p_assignment); + void _extract_from_call(GDScriptParser::CallNode *p_call); + void _extract_fd_literals(GDScriptParser::ExpressionNode *p_expression); public: - virtual Error parse_file(const String &p_path, Vector<String> *r_extracted_strings) override; + virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; virtual void get_recognized_extensions(List<String> *r_extensions) const override; GDScriptEditorTranslationParserPlugin(); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 541d46a2af..859c1acde9 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,19 +32,23 @@ #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" +#ifdef TESTS_ENABLED +#include "tests/gdscript_test_runner.h" +#endif + /////////////////////////// GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) { @@ -219,7 +223,7 @@ StringName GDScript::get_instance_base_type() const { } struct _GDScriptMemberSort { - int index; + int index = 0; StringName name; _FORCE_INLINE_ bool operator<(const _GDScriptMemberSort &p_member) const { return index < p_member.index; } }; @@ -231,7 +235,7 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { } #endif -void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { +void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { const GDScript *current = this; while (current) { for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) { @@ -239,18 +243,29 @@ void GDScript::get_script_method_list(List<MethodInfo> *p_list) const { MethodInfo mi; mi.name = E->key(); for (int i = 0; i < func->get_argument_count(); i++) { - mi.arguments.push_back(func->get_argument_type(i)); + PropertyInfo arginfo = func->get_argument_type(i); +#ifdef TOOLS_ENABLED + arginfo.name = func->get_argument_name(i); +#endif + mi.arguments.push_back(arginfo); } mi.return_val = func->get_return_type(); - p_list->push_back(mi); + r_list->push_back(mi); + } + if (!p_include_base) { + return; } current = current->_base; } } -void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { +void GDScript::get_script_method_list(List<MethodInfo> *r_list) const { + _get_script_method_list(r_list, true); +} + +void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const { const GDScript *sptr = this; List<PropertyInfo> props; @@ -265,19 +280,26 @@ void GDScript::get_script_property_list(List<PropertyInfo> *p_list) const { } msort.sort(); - msort.invert(); + msort.reverse(); for (int i = 0; i < msort.size(); i++) { props.push_front(sptr->member_info[msort[i].name]); } + if (!p_include_base) { + break; + } sptr = sptr->_base; } for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - p_list->push_back(E->get()); + r_list->push_back(E->get()); } } +void GDScript::get_script_property_list(List<PropertyInfo> *r_list) const { + _get_script_property_list(r_list, true); +} + bool GDScript::has_method(const StringName &p_method) const { return member_functions.has(p_method); } @@ -383,6 +405,183 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro propnames.push_back(E->get()); } } + +void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) { + if (_owner) { + _owner->_add_doc(p_inner_class); + } else { + for (int i = 0; i < docs.size(); i++) { + if (docs[i].name == p_inner_class.name) { + docs.remove(i); + break; + } + } + docs.append(p_inner_class); + } +} + +void GDScript::_clear_doc() { + docs.clear(); + doc = DocData::ClassDoc(); +} + +void GDScript::_update_doc() { + _clear_doc(); + + doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; + if (!name.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) { @@ -598,15 +797,13 @@ Error GDScript::reload(bool p_keep_state) { { String source_path = path; - if (source_path.empty()) { + if (source_path.is_empty()) { source_path = get_path(); } - if (!source_path.empty()) { + if (!source_path.is_empty()) { MutexLock lock(GDScriptCache::singleton->lock); - Ref<GDScript> self(this); if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { - GDScriptCache::singleton->shallow_gdscript_cache[source_path] = self; - self->unreference(); + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; } } } @@ -619,7 +816,7 @@ Error GDScript::reload(bool p_keep_state) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); } // TODO: Show all error messages. - _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_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); } @@ -630,8 +827,12 @@ Error GDScript::reload(bool p_keep_state) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); } - // TODO: Show all error messages. - _err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + + const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front(); + while (e != nullptr) { + _err_print_error("GDScript::reload", path.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_FAIL_V(ERR_PARSE_ERROR); } @@ -640,12 +841,16 @@ Error GDScript::reload(bool p_keep_state) { GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); +#ifdef TOOLS_ENABLED + _update_doc(); +#endif + if (err) { if (can_run) { if (EngineDebugger::is_active()) { 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; @@ -913,7 +1118,7 @@ bool GDScript::has_script_signal(const StringName &p_signal) const { return false; } -void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { +void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const { for (const Map<StringName, Vector<StringName>>::Element *E = _signals.front(); E; E = E->next()) { MethodInfo mi; mi.name = E->key(); @@ -922,33 +1127,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; - _base = nullptr; - _owner = nullptr; - tool = false; -#ifdef TOOLS_ENABLED - source_changed_cache = false; - placeholder_fallback_enabled = false; -#endif - #ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->lock); @@ -1046,8 +1263,10 @@ GDScript::~GDScript() { MutexLock lock(GDScriptLanguage::get_singleton()->lock); while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - E->self()->_clear_stack(); + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); + E->self()->_clear_stack(); } } @@ -1055,10 +1274,19 @@ GDScript::~GDScript() { memdelete(E->get()); } - GDScriptCache::remove_script(get_path()); + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. + GDScriptCache::remove_script(get_path()); + } _save_orphaned_subclasses(); +#ifdef TOOLS_ENABLED + // Clearing inner class doc, script doc only cleared when the script source deleted. + if (_owner) { + _clear_doc(); + } +#endif + #ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->lock); @@ -1086,20 +1314,29 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { return true; //function exists, call was successful } } else { - if (!member->data_type.is_type(p_value)) { - // Try conversion - Callable::CallError ce; - const Variant *value = &p_value; - Variant converted = Variant::construct(member->data_type.builtin_type, &value, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - members.write[member->index] = converted; - return true; - } else { - return false; + if (member->data_type.has_type) { + if (member->data_type.builtin_type == Variant::ARRAY && member->data_type.has_container_element_type()) { + // Typed array. + if (p_value.get_type() == Variant::ARRAY) { + return VariantInternal::get_array(&members.write[member->index])->typed_assign(p_value); + } else { + return false; + } + } else if (!member->data_type.is_type(p_value)) { + // Try conversion + Callable::CallError ce; + const Variant *value = &p_value; + 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; + } else { + return false; + } } - } else { - members.write[member->index] = p_value; } + members.write[member->index] = p_value; } return true; } @@ -1269,7 +1506,7 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const } msort.sort(); - msort.invert(); + msort.reverse(); for (int i = 0; i < msort.size(); i++) { props.push_front(sptr->member_info[msort[i].name]); } @@ -1451,8 +1688,10 @@ GDScriptInstance::~GDScriptInstance() { MutexLock lock(GDScriptLanguage::get_singleton()->lock); while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - E->self()->_clear_stack(); + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); + E->self()->_clear_stack(); } if (script.is_valid() && owner) { @@ -1496,9 +1735,9 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { void GDScriptLanguage::init() { //populate global constants - int gcc = GlobalConstants::get_global_constant_count(); + int gcc = CoreConstants::get_global_constant_count(); for (int i = 0; i < gcc; i++) { - _add_global(StaticCString::create(GlobalConstants::get_global_constant_name(i)), GlobalConstants::get_global_constant_value(i)); + _add_global(StaticCString::create(CoreConstants::get_global_constant_name(i)), CoreConstants::get_global_constant_value(i)); } _add_global(StaticCString::create("PI"), Math_PI); @@ -1531,6 +1770,10 @@ void GDScriptLanguage::init() { for (List<Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { _add_global(E->get().name, E->get().ptr); } + +#ifdef TESTS_ENABLED + GDScriptTests::GDScriptTestRunner::handle_cmdline(); +#endif } String GDScriptLanguage::get_type() const { @@ -1884,11 +2127,27 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { 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::is_control_flow_keyword(String p_keyword) const { + return p_keyword == "break" || + p_keyword == "continue" || + p_keyword == "elif" || + p_keyword == "else" || + p_keyword == "if" || + p_keyword == "for" || + p_keyword == "match" || + p_keyword == "pass" || + p_keyword == "return" || + p_keyword == "while"; +} + bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { return p_type == "GDScript"; } @@ -1910,7 +2169,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b 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()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); @@ -1922,7 +2181,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b GDScriptParser subparser; while (subclass) { if (subclass->extends_used) { - if (!subclass->extends_path.empty()) { + if (!subclass->extends_path.is_empty()) { if (subclass->extends.size() == 0) { get_global_class_name(subclass->extends_path, r_base_type); subclass = nullptr; @@ -1936,7 +2195,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b } String subsource = subfile->get_as_utf8_string(); - if (subsource.empty()) { + if (subsource.is_empty()) { break; } String subpath = subclass->extends_path; @@ -2037,6 +2296,32 @@ GDScriptLanguage::~GDScriptLanguage() { if (_call_stack) { memdelete_arr(_call_stack); } + + // 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 = nullptr; } @@ -2060,7 +2345,7 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na /*************** 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) { +RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -2107,7 +2392,7 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S 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; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 9906b4014d..6df66e876d 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,9 +33,10 @@ #include "core/debugger/engine_debugger.h" #include "core/debugger/script_debugger.h" +#include "core/doc_data.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" -#include "core/script_language.h" +#include "core/object/script_language.h" #include "gdscript_function.h" class GDScriptNativeClass : public Reference { @@ -56,11 +57,11 @@ public: class GDScript : public Script { GDCLASS(GDScript, Script); - bool tool; - bool valid; + bool tool = false; + bool valid = false; struct MemberInfo { - int index; + int index = 0; StringName setter; StringName getter; MultiplayerAPI::RPCMode rpc_mode; @@ -69,14 +70,15 @@ 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; - GDScript *_base; //fast pointer access - GDScript *_owner; //for subclasses + GDScript *_base = nullptr; //fast pointer access + GDScript *_owner = nullptr; //for subclasses Set<StringName> members; //members are just indices to the instanced script. Map<StringName, Variant> constants; @@ -90,24 +92,36 @@ 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; Set<ObjectID> inheriters_cache; - bool source_changed_cache; - bool placeholder_fallback_enabled; + bool source_changed_cache = false; + bool placeholder_fallback_enabled = false; 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 *implicit_initializer = nullptr; GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate - int subclass_count; + int subclass_count = 0; Set<Object *> instances; //exported members String source; @@ -140,6 +154,13 @@ class GDScript : public Script { void _save_orphaned_subclasses(); void _init_rpc_methods_properties(); + void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const; + void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const; + void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const; + + // This method will map the class name from "Reference" to "MyClass.InnerClass". + static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -190,6 +211,12 @@ public: virtual void set_source_code(const String &p_code) override; virtual void update_exports() override; +#ifdef TOOLS_ENABLED + virtual const Vector<DocData::ClassDoc> &get_documentation() const override { + return docs; + } +#endif // TOOLS_ENABLED + virtual Error reload(bool p_keep_state = false) override; void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too... @@ -243,8 +270,9 @@ public: class GDScriptInstance : public ScriptInstance { friend class GDScript; friend class GDScriptFunction; - friend class GDScriptFunctions; + friend class GDScriptLambdaCallable; friend class GDScriptCompiler; + friend struct GDScriptUtilityFunctionsDefinitions; ObjectID owner_id; Object *owner; @@ -433,6 +461,7 @@ public: /* EDITOR FUNCTIONS */ virtual void get_reserved_words(List<String> *p_words) const; + virtual bool is_control_flow_keyword(String p_keywords) const; virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; virtual String _get_processed_template(const String &p_template, const String &p_base_class_name) const; @@ -443,7 +472,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); @@ -501,7 +531,7 @@ public: class ResourceFormatLoaderGDScript : public ResourceFormatLoader { public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, bool p_no_cache = false); + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual bool handles_type(const String &p_type) const; virtual String get_resource_type(const String &p_path) const; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7b07cce8bd..17ae52f3ab 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.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,50 +30,46 @@ #include "gdscript_analyzer.h" -#include "core/class_db.h" -#include "core/hash_map.h" +#include "core/config/project_settings.h" #include "core/io/resource_loader.h" +#include "core/object/class_db.h" +#include "core/object/script_language.h" #include "core/os/file_access.h" -#include "core/project_settings.h" -#include "core/script_language.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.empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - -void GDScriptAnalyzer::cleanup() { - underscore_map.clear(); +static 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; } static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) { @@ -116,7 +112,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native type.is_meta_type = true; List<StringName> enum_values; - StringName real_native_name = get_real_class_name(p_native_class); + StringName real_native_name = GDScriptParser::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()) { @@ -175,14 +171,14 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, int extends_index = 0; - if (!p_class->extends_path.empty()) { + 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::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); return err; @@ -190,7 +186,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, base = parser->get_parser()->head->get_datatype(); } else { - if (p_class->extends.empty()) { + if (p_class->extends.is_empty()) { return ERR_PARSE_ERROR; } const StringName &name = p_class->extends[extends_index++]; @@ -208,11 +204,12 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } + base = parser->get_parser()->head->get_datatype(); } } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); @@ -227,12 +224,12 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } - } else if (class_exists(name) && ClassDB::can_instance(get_real_class_name(name))) { + } else if (class_exists(name) && ClassDB::can_instance(GDScriptParser::get_real_class_name(name))) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -302,6 +299,16 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } + // Check for cyclic inheritance. + const GDScriptParser::ClassNode *base_class = result.class_type; + while (base_class) { + if (base_class->fqcn == p_class->fqcn) { + push_error("Cyclic inheritance.", p_class); + return ERR_PARSE_ERROR; + } + base_class = base_class->base_type.class_type; + } + p_class->base_type = result; class_type.native_type = result.native_type; p_class->set_datatype(class_type); @@ -309,7 +316,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { - resolve_inheritance(p_class->members[i].m_class, true); + Error err = resolve_inheritance(p_class->members[i].m_class, true); + if (err) { + return err; + } } } } @@ -328,7 +338,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.type_source = result.ANNOTATED_EXPLICIT; result.builtin_type = Variant::OBJECT; - if (p_type->type_chain.empty()) { + if (p_type->type_chain.is_empty()) { // void. result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::NIL; @@ -365,6 +375,14 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = GDScriptParser::get_builtin_type(first); + + if (result.builtin_type == Variant::ARRAY) { + GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type); + + if (container_type.kind != GDScriptParser::DataType::VARIANT) { + result.set_container_element_type(container_type); + } + } } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; @@ -388,7 +406,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::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)) { + } else if (ClassDB::has_enum(GDScriptParser::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 { @@ -451,7 +469,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } 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 (ClassDB::has_enum(GDScriptParser::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 { @@ -465,6 +483,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } + if (result.builtin_type != Variant::ARRAY && p_type->container_type != nullptr) { + push_error("Only arrays can specify the collection element type.", p_type); + } + p_type->set_datatype(result); return result; } @@ -487,15 +509,31 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; + GDScriptParser::DataType specified_type; + if (member.variable->datatype_specifier != nullptr) { + specified_type = resolve_datatype(member.variable->datatype_specifier); + specified_type.is_meta_type = false; + } + if (member.variable->initializer != nullptr) { member.variable->set_datatype(datatype); // Allow recursive usage. reduce_expression(member.variable->initializer); + if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) { + // Typed array. + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer); + // Can only infer typed array if it has elements. + if ((member.variable->infer_datatype && array->elements.size() > 0) || member.variable->datatype_specifier != nullptr) { + update_array_literal_element_type(specified_type, array); + } + } 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; + datatype = specified_type; if (member.variable->initializer != nullptr) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { @@ -526,45 +564,47 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } else if (datatype.builtin_type == Variant::NIL) { push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); } + datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; } datatype.is_constant = false; member.variable->set_datatype(datatype); - if (!datatype.has_no_type()) { - // TODO: Move this out into a routine specific to validate annotations. - if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) { - // @export annotation. - switch (datatype.kind) { - case GDScriptParser::DataType::BUILTIN: - member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type); - break; - case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) { - member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); - } else { - push_error(R"(Export type can only be built-in or a resource.)", member.variable); - } - break; - default: - // TODO: Allow custom user resources. - push_error(R"(Export type can only be built-in or a resource.)", member.variable); - break; - } - } + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.variable->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.variable); } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { reduce_expression(member.constant->initializer); + GDScriptParser::DataType specified_type; + + if (member.constant->datatype_specifier != nullptr) { + specified_type = resolve_datatype(member.constant->datatype_specifier); + specified_type.is_meta_type = false; + } + GDScriptParser::DataType datatype = member.constant->get_datatype(); if (member.constant->initializer) { + if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); + const_fold_array(array); + + // Can only infer typed array if it has elements. + if (array->elements.size() > 0 || (member.constant->datatype_specifier != nullptr && specified_type.has_container_element_type())) { + update_array_literal_element_type(specified_type, array); + } + } 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; + datatype = specified_type; 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); @@ -578,6 +618,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.is_constant = true; member.constant->set_datatype(datatype); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.constant->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.constant); + } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { for (int j = 0; j < member.signal->parameters.size(); j++) { @@ -592,6 +637,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas signal_type.builtin_type = Variant::SIGNAL; member.signal->set_datatype(signal_type); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.signal->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.signal); + } } break; case GDScriptParser::ClassNode::Member::ENUM: { GDScriptParser::DataType enum_type; @@ -634,6 +684,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas current_enum = nullptr; member.m_enum->set_datatype(enum_type); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.m_enum->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.m_enum); + } } break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); @@ -702,6 +757,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } resolve_function_body(member.function); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.function->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.function); + } } parser->current_class = previous_class; @@ -775,7 +835,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { resolve_match_branch(static_cast<GDScriptParser::MatchBranchNode *>(p_node), nullptr); break; case GDScriptParser::Node::PARAMETER: - resolve_pararameter(static_cast<GDScriptParser::ParameterNode *>(p_node)); + resolve_parameter(static_cast<GDScriptParser::ParameterNode *>(p_node)); break; case GDScriptParser::Node::PATTERN: resolve_match_pattern(static_cast<GDScriptParser::PatternNode *>(p_node), nullptr); @@ -796,6 +856,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::DICTIONARY: case GDScriptParser::Node::GET_NODE: case GDScriptParser::Node::IDENTIFIER: + case GDScriptParser::Node::LAMBDA: case GDScriptParser::Node::LITERAL: case GDScriptParser::Node::PRELOAD: case GDScriptParser::Node::SELF: @@ -829,13 +890,18 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * parser->current_function = p_function; for (int i = 0; i < p_function->parameters.size(); i++) { - resolve_pararameter(p_function->parameters[i]); + resolve_parameter(p_function->parameters[i]); #ifdef DEBUG_ENABLED if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); } is_shadowing(p_function->parameters[i]->identifier, "function parameter"); -#endif +#endif // DEBUG_ENABLED +#ifdef TOOLS_ENABLED + if (p_function->parameters[i]->default_value && p_function->parameters[i]->default_value->is_constant) { + p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } +#endif // TOOLS_ENABLED } if (p_function->identifier->name == "_init") { @@ -899,6 +965,7 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript p_suite->datatype.type_source = GDScriptParser::DataType::UNDETECTED; } else { p_suite->set_datatype(p_statement->get_datatype()); + p_suite->datatype.type_source = GDScriptParser::DataType::INFERRED; } break; default: @@ -952,17 +1019,19 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { if (!call->arguments[i]->is_constant) { all_is_constant = false; - } else { + } else if (all_is_constant) { args.write[i] = call->arguments[i]->reduced_value; } GDScriptParser::DataType arg_type = call->arguments[i]->get_datatype(); - if (arg_type.kind != GDScriptParser::DataType::BUILTIN) { - all_is_constant = false; - push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); - } else if (arg_type.builtin_type != Variant::INT && arg_type.builtin_type != Variant::FLOAT) { - all_is_constant = false; - push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + if (!arg_type.is_variant()) { + if (arg_type.kind != GDScriptParser::DataType::BUILTIN) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } else if (arg_type.builtin_type != Variant::INT && arg_type.builtin_type != Variant::FLOAT) { + all_is_constant = false; + push_error(vformat(R"*(Invalid argument for "range()" call. Argument %d should be int or float but "%s" was given.)*", i + 1, arg_type.to_string()), call->arguments[i]); + } } } @@ -985,11 +1054,15 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } } - GDScriptParser::DataType list_type; - list_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - list_type.kind = GDScriptParser::DataType::BUILTIN; - list_type.builtin_type = Variant::ARRAY; - p_for->list->set_datatype(list_type); + if (p_for->list->is_constant) { + p_for->list->set_datatype(type_from_variant(p_for->list->reduced_value, p_for->list)); + } else { + GDScriptParser::DataType list_type; + list_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + list_type.kind = GDScriptParser::DataType::BUILTIN; + list_type.builtin_type = Variant::ARRAY; + p_for->list->set_datatype(list_type); + } } } } @@ -1021,8 +1094,23 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable GDScriptParser::DataType type; type.kind = GDScriptParser::DataType::VARIANT; // By default. + GDScriptParser::DataType specified_type; + if (p_variable->datatype_specifier != nullptr) { + specified_type = resolve_datatype(p_variable->datatype_specifier); + specified_type.is_meta_type = false; + } + if (p_variable->initializer != nullptr) { reduce_expression(p_variable->initializer); + if ((p_variable->infer_datatype || (p_variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && p_variable->initializer->type == GDScriptParser::Node::ARRAY) { + // Typed array. + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_variable->initializer); + // Can only infer typed array if it has elements. + if ((p_variable->infer_datatype && array->elements.size() > 0) || p_variable->datatype_specifier != nullptr) { + update_array_literal_element_type(specified_type, array); + } + } + type = p_variable->initializer->get_datatype(); if (p_variable->infer_datatype) { @@ -1046,7 +1134,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } if (p_variable->datatype_specifier != nullptr) { - type = resolve_datatype(p_variable->datatype_specifier); + type = specified_type; type.is_meta_type = false; if (p_variable->initializer != nullptr) { @@ -1092,19 +1180,26 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { GDScriptParser::DataType type; - reduce_expression(p_constant->initializer); + 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); - } + 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(); + 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); - } + 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); @@ -1139,7 +1234,10 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { reduce_expression(p_assert->condition); if (p_assert->message != nullptr) { - reduce_literal(p_assert->message); + 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()); @@ -1244,14 +1342,19 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc p_match_pattern->set_datatype(result); } -void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_parameter) { +void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) { GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::VARIANT; if (p_parameter->default_value != nullptr) { reduce_expression(p_parameter->default_value); result = p_parameter->default_value->get_datatype(); - result.type_source = GDScriptParser::DataType::INFERRED; + if (p_parameter->infer_datatype) { + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else { + result.type_source = GDScriptParser::DataType::INFERRED; + } + result.is_constant = false; } if (p_parameter->datatype_specifier != nullptr) { @@ -1261,7 +1364,7 @@ void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_para if (p_parameter->default_value != nullptr) { if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) { - push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with paremeter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); + push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with parameter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); } else if (p_parameter->default_value->get_datatype().is_variant()) { mark_node_unsafe(p_parameter); } @@ -1276,6 +1379,12 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { if (p_return->return_value != nullptr) { reduce_expression(p_return->return_value); + if (p_return->return_value->type == GDScriptParser::Node::ARRAY) { + // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. + if (parser->current_function->get_datatype().has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(parser->current_function->get_datatype(), static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); + } + } result = p_return->return_value->get_datatype(); } else { // Return type is null by default. @@ -1350,6 +1459,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::IDENTIFIER: reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_expression)); break; + case GDScriptParser::Node::LAMBDA: + reduce_lambda(static_cast<GDScriptParser::LambdaNode *>(p_expression)); + break; case GDScriptParser::Node::LITERAL: reduce_literal(static_cast<GDScriptParser::LiteralNode *>(p_expression)); break; @@ -1397,22 +1509,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre } void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { - bool all_is_constant = true; - for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element = p_array->elements[i]; reduce_expression(element); - all_is_constant = all_is_constant && element->is_constant; - } - - if (all_is_constant) { - Array array; - array.resize(p_array->elements.size()); - for (int i = 0; i < p_array->elements.size(); i++) { - array[i] = p_array->elements[i]->reduced_value; - } - p_array->is_constant = true; - p_array->reduced_value = array; } // It's array in any case. @@ -1425,6 +1524,52 @@ void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { p_array->set_datatype(arr_type); } +// When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed. +// This function determines which type is that (if any). +void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) { + GDScriptParser::DataType array_type = p_array_literal->get_datatype(); + if (p_array_literal->elements.size() == 0) { + // Empty array literal, just make the same type as the storage. + array_type.set_container_element_type(p_base_type.get_container_element_type()); + } else { + // Check if elements match. + bool all_same_type = true; + bool all_have_type = true; + + GDScriptParser::DataType element_type; + for (int i = 0; i < p_array_literal->elements.size(); i++) { + if (i == 0) { + element_type = p_array_literal->elements[0]->get_datatype(); + } else { + GDScriptParser::DataType this_element_type = p_array_literal->elements[i]->get_datatype(); + if (this_element_type.has_no_type()) { + all_same_type = false; + all_have_type = false; + break; + } else if (element_type != this_element_type) { + if (!is_type_compatible(element_type, this_element_type, false)) { + if (is_type_compatible(this_element_type, element_type, false)) { + // This element is a super-type to the previous type, so we use the super-type. + element_type = this_element_type; + } else { + // It's incompatible. + all_same_type = false; + break; + } + } + } + } + } + if (all_same_type) { + array_type.set_container_element_type(element_type); + } else if (all_have_type) { + push_error(vformat(R"(Variant array is not compatible with an array of type "%s".)", p_base_type.get_container_element_type().to_string()), p_array_literal); + } + } + // Update the type on the value itself. + p_array_literal->set_datatype(array_type); +} + void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { reduce_expression(p_assignment->assignee); reduce_expression(p_assignment->assigned_value); @@ -1433,24 +1578,33 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig return; } - if (p_assignment->assignee->get_datatype().is_constant) { + GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); + + // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. + if (assignee_type.has_container_element_type() && p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value)); + } + + GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype(); + + if (assignee_type.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()) { + if (!assignee_type.is_variant() && !assigned_value_type.is_variant()) { bool compatible = true; - GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype(); + GDScriptParser::DataType op_type = assigned_value_type; 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); + op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value); } if (compatible) { - compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true); + compatible = is_type_compatible(assignee_type, op_type, true); if (!compatible) { - if (p_assignment->assignee->get_datatype().is_hard_type()) { + if (assignee_type.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); + if (!is_type_compatible(op_type, assignee_type, true)) { + push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } else { // TODO: Add warning. mark_node_unsafe(p_assignment); @@ -1461,11 +1615,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } } 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); + push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment); } } - if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) { + if (assignee_type.has_no_type() || assigned_value_type.is_variant()) { mark_node_unsafe(p_assignment); } @@ -1485,7 +1639,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig 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 = assigned_value_type; id_type.type_source = GDScriptParser::DataType::INFERRED; id_type.is_constant = false; identifier->variable_source->set_datatype(id_type); @@ -1494,7 +1648,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig 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 = assigned_value_type; id_type.type_source = GDScriptParser::DataType::INFERRED; id_type.is_constant = false; identifier->variable_source->set_datatype(id_type); @@ -1506,12 +1660,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } - 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) { + if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_value_type.kind == GDScriptParser::DataType::BUILTIN && assigned_value_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) { + } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); } #endif @@ -1582,7 +1734,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o if (p_binary_op->reduced_value.get_type() == Variant::STRING) { push_error(vformat(R"(%s in operator %s.)", p_binary_op->reduced_value, Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); } else { - push_error(vformat(R"(Invalid operands to operator %s, %s and %s.".)", + push_error(vformat(R"(Invalid operands to operator %s, %s and %s.)", Variant::get_operator_name(p_binary_op->variant_op), Variant::get_type_name(p_binary_op->left_operand->reduced_value.get_type()), Variant::get_type_name(p_binary_op->right_operand->reduced_value.get_type())), @@ -1604,7 +1756,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o ERR_PRINT("Parser bug: unknown binary operation."); } } - p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value)); + p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value, p_binary_op)); return; } @@ -1618,7 +1770,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o } else { if (p_binary_op->variant_op < Variant::OP_MAX) { bool valid = false; - result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid); + result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid, p_binary_op); if (!valid) { push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); @@ -1655,8 +1807,12 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_await) { bool all_is_constant = true; + Map<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. for (int i = 0; i < p_call->arguments.size(); i++) { reduce_expression(p_call->arguments[i]); + if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) { + arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]); + } all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; } @@ -1667,7 +1823,6 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa // Call to name directly. StringName function_name = p_call->function_name; Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); - GDScriptFunctions::Function builtin_function = GDScriptParser::get_builtin_function(function_name); if (builtin_type < Variant::VARIANT_MAX) { // Is a builtin constructor. @@ -1680,7 +1835,29 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa call_type.native_type = function_name; // "Object". } - if (all_is_constant) { + bool safe_to_fold = true; + switch (builtin_type) { + // Those are stored by reference so not suited for compile-time construction. + // Because in this case they would be the same reference in all constructed values. + case Variant::OBJECT: + case Variant::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++) { @@ -1688,7 +1865,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } Callable::CallError err; - Variant value = Variant::construct(builtin_type, (const Variant **)args.ptr(), args.size(), err); + Variant value; + Variant::construct(builtin_type, value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: @@ -1788,10 +1966,52 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } p_call->set_datatype(call_type); return; - } else if (builtin_function < GDScriptFunctions::FUNC_MAX) { - MethodInfo function_info = GDScriptFunctions::get_info(builtin_function); + } 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 && GDScriptFunctions::is_deterministic(builtin_function)) { + 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++) { @@ -1800,23 +2020,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa Variant value; Callable::CallError err; - GDScriptFunctions::call(builtin_function, (const Variant **)args.ptr(), args.size(), value, 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.)*", GDScriptFunctions::get_func_name(builtin_function), err.argument + 1, + 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".)", GDScriptFunctions::get_func_name(builtin_function)), p_call); + 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.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + 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.)*", GDScriptFunctions::get_func_name(builtin_function), err.expected, p_call->arguments.size()), p_call); + 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. @@ -1845,6 +2065,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa is_self = true; } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) { GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee); + if (subscript->base == nullptr) { + // Invalid syntax, error already set on parser. + p_call->set_datatype(call_type); + mark_node_unsafe(p_call); + return; + } if (!subscript->is_attribute) { // Invalid call. Error already sent in parser. // TODO: Could check if Callable here. @@ -1870,10 +2096,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa 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)) { + // If the function require typed arrays we must make literals be typed. + for (Map<int, GDScriptParser::ArrayNode *>::Element *E = arrays.front(); E; E = E->next()) { + int index = E->key(); + if (index < par_types.size() && par_types[index].has_container_element_type()) { + update_array_literal_element_type(par_types[index], E->get()); + } + } 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); + } else if (is_self && !is_static && !lambda_stack.is_empty()) { + push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call->callee); } call_type = return_type; @@ -1899,14 +2134,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa 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) { + } 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) { + 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); } @@ -1959,8 +2194,6 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - bool all_is_constant = true; - HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { @@ -1969,7 +2202,6 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti reduce_expression(element.key); } reduce_expression(element.value); - all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; if (element.key->is_constant) { if (elements.has(element.key->reduced_value)) { @@ -1980,16 +2212,6 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti } } - if (all_is_constant) { - Dictionary dict; - for (int i = 0; i < p_dictionary->elements.size(); i++) { - const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; - dict[element.key->reduced_value] = element.value->reduced_value; - } - p_dictionary->is_constant = true; - p_dictionary->reduced_value = dict; - } - // It's dictionary in any case. GDScriptParser::DataType dict_type; dict_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2007,16 +2229,26 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) 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)) { + if (!ClassDB::is_parent_class(GDScriptParser::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); + } else if (!lambda_stack.is_empty()) { + push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node); } p_get_node->set_datatype(result); } -GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name) { +GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source) { Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(p_class_name)); - ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + + if (err) { + push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source); + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::UNDETECTED; + type.kind = GDScriptParser::DataType::VARIANT; + return type; + } GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2047,23 +2279,38 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = result; - p_identifier->set_datatype(type_from_variant(result)); + p_identifier->set_datatype(type_from_variant(result, p_identifier)); } else { push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); } } else { - Callable::CallError temp; - Variant dummy = Variant::construct(base.builtin_type, nullptr, 0, temp); - List<PropertyInfo> properties; - dummy.get_property_list(&properties); - for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) { - const PropertyInfo &prop = E->get(); - if (prop.name == name) { - p_identifier->set_datatype(type_from_property(prop)); + switch (base.builtin_type) { + case Variant::NIL: { + push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); return; } + case Variant::DICTIONARY: { + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_identifier->set_datatype(dummy); + return; + } + default: { + Callable::CallError temp; + Variant dummy; + Variant::construct(base.builtin_type, dummy, nullptr, 0, temp); + List<PropertyInfo> properties; + dummy.get_property_list(&properties); + for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == name) { + p_identifier->set_datatype(type_from_property(prop)); + return; + } + } + push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + } } - push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); } return; } @@ -2113,6 +2360,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::ENUM_VALUE: p_identifier->is_constant = true; p_identifier->reduced_value = member.enum_value.value; + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; break; case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; @@ -2150,7 +2398,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check native members. - const StringName &native = get_real_class_name(base.native_type); + const StringName &native = GDScriptParser::get_real_class_name(base.native_type); if (class_exists(native)) { PropertyInfo prop_info; @@ -2178,7 +2426,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = int_constant; - p_identifier->set_datatype(type_from_variant(int_constant)); + p_identifier->set_datatype(type_from_variant(int_constant, p_identifier)); return; } } @@ -2213,52 +2461,77 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } + bool found_source = false; // 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; + found_source = true; + break; 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; + found_source = true; + break; 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; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: p_identifier->set_datatype(p_identifier->bind_source->get_datatype()); - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_BIND: { GDScriptParser::DataType result = p_identifier->bind_source->get_datatype(); result.is_constant = true; p_identifier->set_datatype(result); - return; - } + found_source = true; + } break; 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. + if (!found_source) { + reduce_identifier_from_base(p_identifier); + if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { + // Found. + found_source = true; + } + } + + if (found_source) { + // If the identifier is local, check if it's any kind of capture by comparing their source function. + // Only capture locals and members and enum values. Constants are still accessible from the lambda using the script reference. + if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT || lambda_stack.is_empty()) { + return; + } + + GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function; + while (function_test != nullptr && function_test != p_identifier->source_function && function_test->source_lambda != nullptr && !function_test->source_lambda->captures_indices.has(p_identifier->name)) { + function_test->source_lambda->captures_indices[p_identifier->name] = function_test->source_lambda->captures.size(); + function_test->source_lambda->captures.push_back(p_identifier); + function_test = function_test->source_lambda->parent_function; + } return; } StringName name = p_identifier->name; p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; - // Check globals. - if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { + // Check globals. We make an exception for Variant::OBJECT because it's the base class for + // non-builtin types so we allow doing e.g. Object.new() + Variant::Type builtin_type = GDScriptParser::get_builtin_type(name); + if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) { if (can_be_builtin) { - p_identifier->set_datatype(make_builtin_meta_type(GDScriptParser::get_builtin_type(name))); + p_identifier->set_datatype(make_builtin_meta_type(builtin_type)); return; } else { push_error(R"(Builtin type cannot be used as a name on its own.)", p_identifier); @@ -2271,14 +2544,38 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } if (ScriptServer::is_global_class(name)) { - p_identifier->set_datatype(make_global_class_meta_type(name)); + p_identifier->set_datatype(make_global_class_meta_type(name, p_identifier)); return; } + // Try singletons. + // Do this before globals because this might be a singleton loading another one before it's compiled. + if (ProjectSettings::get_singleton()->has_autoload(name)) { + const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(name); + if (autoload.is_singleton) { + // Singleton exists, so it's at least a Node. + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::NATIVE; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { + Ref<GDScriptParserRef> parser = get_parser_for(autoload.path); + if (parser.is_valid()) { + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(parser->get_parser()->head->get_datatype()); + } + } + } + result.is_constant = true; + p_identifier->set_datatype(result); + return; + } + } + if (GDScriptLanguage::get_singleton()->get_global_map().has(name)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[name]; Variant constant = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->set_datatype(type_from_variant(constant, p_identifier)); p_identifier->is_constant = true; p_identifier->reduced_value = constant; return; @@ -2286,7 +2583,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; - p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->set_datatype(type_from_variant(constant, p_identifier)); p_identifier->is_constant = true; p_identifier->reduced_value = constant; return; @@ -2294,7 +2591,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident // Not found. // Check if it's a builtin function. - if (parser->get_builtin_function(name) < GDScriptFunctions::FUNC_MAX) { + 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); @@ -2304,11 +2601,62 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->set_datatype(dummy); // Just so type is set to something. } +void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { + // Lambda is always a Callable. + GDScriptParser::DataType lambda_type; + lambda_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + lambda_type.kind = GDScriptParser::DataType::BUILTIN; + lambda_type.builtin_type = Variant::CALLABLE; + p_lambda->set_datatype(lambda_type); + + if (p_lambda->function == nullptr) { + return; + } + + GDScriptParser::FunctionNode *previous_function = parser->current_function; + parser->current_function = p_lambda->function; + + lambda_stack.push_back(p_lambda); + + for (int i = 0; i < p_lambda->function->parameters.size(); i++) { + resolve_parameter(p_lambda->function->parameters[i]); + } + + resolve_suite(p_lambda->function->body); + + int captures_amount = p_lambda->captures.size(); + if (captures_amount > 0) { + // Create space for lambda parameters. + // At the beginning to not mess with optional parameters. + int param_count = p_lambda->function->parameters.size(); + p_lambda->function->parameters.resize(param_count + captures_amount); + for (int i = param_count - 1; i >= 0; i--) { + p_lambda->function->parameters.write[i + captures_amount] = p_lambda->function->parameters[i]; + p_lambda->function->parameters_indices[p_lambda->function->parameters[i]->identifier->name] = i + captures_amount; + } + + // Add captures as extra parameters at the beginning. + for (int i = 0; i < p_lambda->captures.size(); i++) { + GDScriptParser::IdentifierNode *capture = p_lambda->captures[i]; + GDScriptParser::ParameterNode *capture_param = parser->alloc_node<GDScriptParser::ParameterNode>(); + capture_param->identifier = capture; + capture_param->usages = capture->usages; + capture_param->set_datatype(capture->get_datatype()); + + p_lambda->function->parameters.write[i] = capture_param; + p_lambda->function->parameters_indices[capture->name] = i; + } + } + + lambda_stack.pop_back(); + parser->current_function = previous_function; +} + void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { p_literal->reduced_value = p_literal->value; p_literal->is_constant = true; - p_literal->set_datatype(type_from_variant(p_literal->reduced_value)); + p_literal->set_datatype(type_from_variant(p_literal->reduced_value, p_literal)); } void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { @@ -2345,7 +2693,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { p_preload->is_constant = true; p_preload->reduced_value = p_preload->resource; - p_preload->set_datatype(type_from_variant(p_preload->reduced_value)); + p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload)); } void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { @@ -2362,25 +2710,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri 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; @@ -2388,13 +2717,13 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri if (p_subscript->base->is_constant) { // Just try to get it. bool valid = false; - Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, &valid); + Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); if (!valid) { push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); } else { p_subscript->is_constant = true; p_subscript->reduced_value = value; - result_type = type_from_variant(value); + result_type = type_from_variant(value, p_subscript); } result_type.kind = GDScriptParser::DataType::VARIANT; } else { @@ -2423,7 +2752,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } } else { - // Index was already reduced before. + if (p_subscript->index == nullptr) { + return; + } + reduce_expression(p_subscript->index); if (p_subscript->base->is_constant && p_subscript->index->is_constant) { // Just try to get it. @@ -2434,7 +2766,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } else { p_subscript->is_constant = true; p_subscript->reduced_value = value; - result_type = type_from_variant(value); + result_type = type_from_variant(value, p_subscript); } result_type.kind = GDScriptParser::DataType::VARIANT; } else { @@ -2489,7 +2821,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; break; // Don't support indexing, but we will check it later. - case Variant::_RID: + case Variant::RID: case Variant::BOOL: case Variant::CALLABLE: case Variant::FLOAT: @@ -2522,7 +2854,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri switch (base_type.builtin_type) { // Can't index at all. - case Variant::_RID: + case Variant::RID: case Variant::BOOL: case Variant::CALLABLE: case Variant::FLOAT: @@ -2579,11 +2911,20 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri 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; + // Can have an element type. + case Variant::ARRAY: + if (base_type.has_container_element_type()) { + result_type = base_type.get_container_element_type(); + result_type.type_source = base_type.type_source; + } else { + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + break; // Here for completeness. case Variant::OBJECT: case Variant::VARIANT_MAX: @@ -2658,13 +2999,13 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) if (p_unary_op->operand->is_constant) { p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); - result = type_from_variant(p_unary_op->reduced_value); + result = type_from_variant(p_unary_op->reduced_value, p_unary_op); } else if (p_unary_op->operand->get_datatype().is_variant()) { result.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_unary_op); } else { bool valid = false; - result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), p_unary_op->operand->get_datatype(), valid); + result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), valid, p_unary_op); if (!valid) { push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); @@ -2674,7 +3015,47 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->set_datatype(result); } -GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value) { +void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { + bool all_is_constant = true; + + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element = p_array->elements[i]; + all_is_constant = all_is_constant && element->is_constant; + if (!all_is_constant) { + return; + } + } + + Array array; + array.resize(p_array->elements.size()); + for (int i = 0; i < p_array->elements.size(); i++) { + array[i] = p_array->elements[i]->reduced_value; + } + p_array->is_constant = true; + p_array->reduced_value = array; +} + +void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { + bool all_is_constant = true; + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; + if (!all_is_constant) { + return; + } + } + + Dictionary dict; + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + dict[element.key->reduced_value] = element.value->reduced_value; + } + p_dictionary->is_constant = true; + p_dictionary->reduced_value = dict; +} + +GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.is_constant = true; result.kind = GDScriptParser::DataType::BUILTIN; @@ -2696,19 +3077,44 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va scr = obj->get_script(); } if (scr.is_valid()) { - result.script_type = scr; - result.script_path = scr->get_path(); - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = GDScriptParser::DataType::CLASS; - Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path()); - ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - result.class_type = ref->get_parser()->head; - result.script_path = ref->get_parser()->script_path; + if (scr->is_valid()) { + result.script_type = scr; + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } + + Ref<GDScriptParserRef> ref = get_parser_for(current->path); + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + + GDScriptParser::ClassNode *found = ref->get_parser()->head; + + // It should be okay to assume this exists, since we have a complete script already. + for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) { + found = found->get_member(E->get()).m_class; + } + + result.class_type = found; + result.script_path = ref->get_parser()->script_path; + } else { + result.kind = GDScriptParser::DataType::SCRIPT; + } + result.native_type = scr->get_instance_base_type(); } else { - result.kind = GDScriptParser::DataType::SCRIPT; + push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); + result.kind = GDScriptParser::DataType::VARIANT; + result.type_source = GDScriptParser::DataType::UNDETECTED; + result.is_meta_type = false; } - result.native_type = scr->get_instance_base_type(); } else { result.kind = GDScriptParser::DataType::NATIVE; if (result.native_type == GDScriptNativeClass::get_class_static()) { @@ -2741,6 +3147,34 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; } else { result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = p_property.type; + if (p_property.type == Variant::ARRAY && p_property.hint == PROPERTY_HINT_ARRAY_TYPE) { + // Check element type. + StringName elem_type_name = p_property.hint_string; + GDScriptParser::DataType elem_type; + elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type elem_builtin_type = GDScriptParser::get_builtin_type(elem_type_name); + if (elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + elem_type.kind = GDScriptParser::DataType::BUILTIN; + elem_type.builtin_type = elem_builtin_type; + } else if (class_exists(elem_type_name)) { + elem_type.kind = GDScriptParser::DataType::NATIVE; + elem_type.builtin_type = Variant::OBJECT; + elem_type.native_type = p_property.hint_string; + } else if (ScriptServer::is_global_class(elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(elem_type_name)); + elem_type.kind = GDScriptParser::DataType::SCRIPT; + elem_type.builtin_type = Variant::OBJECT; + elem_type.native_type = script->get_instance_base_type(); + elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array."); + } + result.set_container_element_type(elem_type); + } } return result; } @@ -2754,7 +3188,8 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { // Construct a base type to get methods. Callable::CallError err; - Variant dummy = Variant::construct(p_base_type.builtin_type, nullptr, 0, err); + Variant dummy; + Variant::construct(p_base_type.builtin_type, dummy, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { ERR_FAIL_V_MSG(false, "Could not construct base Variant type."); } @@ -2845,7 +3280,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD return true; } - StringName real_native = get_real_class_name(base_native); + StringName real_native = GDScriptParser::get_real_class_name(base_native); MethodInfo info; if (ClassDB::get_method_info(real_native, function_name, &info)) { @@ -2899,7 +3334,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p if (arg_type.is_variant()) { // Argument can be anything, so this is unsafe. mark_node_unsafe(p_call->arguments[i]); - } else if (!is_type_compatible(par_type, arg_type, true)) { + } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { // Supertypes are acceptable for dynamic compliance, but it's unsafe. mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { @@ -2940,7 +3375,7 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con StringName parent = base_native; while (parent != StringName()) { - StringName real_class_name = get_real_class_name(parent); + StringName real_class_name = GDScriptParser::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; @@ -2964,81 +3399,31 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con } #endif -GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid) { - // This function creates dummy variant values and apply the operation to those. Less error-prone than keeping a table of valid operations. +GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) { + // Unary version. + GDScriptParser::DataType nil_type; + nil_type.builtin_type = Variant::NIL; + return get_operation_type(p_operation, p_a, nil_type, r_valid, p_source); +} +GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::VARIANT; Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; - Variant a; - REF a_ref; - if (a_type == Variant::OBJECT) { - a_ref.instance(); - a = a_ref; - } else { - Callable::CallError err; - a = Variant::construct(a_type, nullptr, 0, err); - if (err.error != Callable::CallError::CALL_OK) { - r_valid = false; - ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(a_type))); - } - } - Variant b; - REF b_ref; - if (b_type == Variant::OBJECT) { - b_ref.instance(); - b = b_ref; - } else { - Callable::CallError err; - b = Variant::construct(b_type, nullptr, 0, err); - if (err.error != Callable::CallError::CALL_OK) { - r_valid = false; - ERR_FAIL_V_MSG(result, vformat("Could not construct value of type %s", Variant::get_type_name(b_type))); - } - } + Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); - // Avoid division by zero. - switch (b_type) { - case Variant::INT: - b = 1; - break; - case Variant::FLOAT: - b = 1.0; - break; - case Variant::VECTOR2: - b = Vector2(1.0, 1.0); - break; - case Variant::VECTOR2I: - b = Vector2i(1, 1); - break; - case Variant::VECTOR3: - b = Vector3(1.0, 1.0, 1.0); - break; - case Variant::VECTOR3I: - b = Vector3i(1, 1, 1); - break; - case Variant::COLOR: - b = Color(1.0, 1.0, 1.0, 1.0); - break; - default: - // No change needed. - break; - } - - // Avoid error in formatting operator (%) where it doesn't find a placeholder. - if (a_type == Variant::STRING && b_type != Variant::ARRAY) { - a = String("%s"); + if (op_eval == nullptr) { + r_valid = false; + return result; } - Variant ret; - Variant::evaluate(p_operation, a, b, ret, r_valid); + r_valid = true; - if (r_valid) { - return type_from_variant(ret); - } + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); return result; } @@ -3068,6 +3453,18 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ // Enum value is also integer. valid = true; } + if (valid && p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) { + // Check the element type. + if (p_target.has_container_element_type()) { + if (!p_source.has_container_element_type()) { + // TODO: Maybe this is valid but unsafe? + // Variant array can't be appended to typed array. + valid = false; + } else { + valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), false); + } + } + } return valid; } @@ -3140,14 +3537,14 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ } // Get underscore-prefixed version for some classes. - src_native = get_real_class_name(src_native); + src_native = GDScriptParser::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); + StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type); return ClassDB::is_parent_class(src_native, tgt_native); } case GDScriptParser::DataType::SCRIPT: @@ -3196,8 +3593,8 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { #endif } -bool GDScriptAnalyzer::class_exists(const StringName &p_class) { - StringName real_name = get_real_class_name(p_class); +bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { + StringName real_name = GDScriptParser::get_real_class_name(p_class); return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); } @@ -3220,12 +3617,12 @@ Error GDScriptAnalyzer::resolve_inheritance() { Error GDScriptAnalyzer::resolve_interface() { resolve_class_interface(parser->head); - return parser->errors.empty() ? OK : ERR_PARSE_ERROR; + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::resolve_body() { resolve_class_body(parser->head); - return parser->errors.empty() ? OK : ERR_PARSE_ERROR; + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::resolve_program() { @@ -3235,10 +3632,12 @@ Error GDScriptAnalyzer::resolve_program() { List<String> parser_keys; depended_parsers.get_key_list(&parser_keys); for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) { + if (depended_parsers[E->get()].is_null()) { + return ERR_PARSE_ERROR; + } depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED); } - depended_parsers.clear(); - return parser->errors.empty() ? OK : ERR_PARSE_ERROR; + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::analyze() { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index da767522ad..aabf407c76 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.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,9 +31,9 @@ #ifndef GDSCRIPT_ANALYZER_H #define GDSCRIPT_ANALYZER_H -#include "core/object.h" -#include "core/reference.h" -#include "core/set.h" +#include "core/object/object.h" +#include "core/object/reference.h" +#include "core/templates/set.h" #include "gdscript_cache.h" #include "gdscript_parser.h" @@ -42,6 +42,7 @@ class GDScriptAnalyzer { HashMap<String, Ref<GDScriptParserRef>> depended_parsers; const GDScriptParser::EnumNode *current_enum = nullptr; + List<const GDScriptParser::LambdaNode *> lambda_stack; Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); @@ -67,7 +68,7 @@ class GDScriptAnalyzer { void resolve_match(GDScriptParser::MatchNode *p_match); void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test); void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test); - void resolve_pararameter(GDScriptParser::ParameterNode *p_parameter); + void resolve_parameter(GDScriptParser::ParameterNode *p_parameter); void resolve_return(GDScriptParser::ReturnNode *p_return); // Reduction functions. @@ -82,6 +83,7 @@ class GDScriptAnalyzer { 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_lambda(GDScriptParser::LambdaNode *p_lambda); void reduce_literal(GDScriptParser::LiteralNode *p_literal); void reduce_preload(GDScriptParser::PreloadNode *p_preload); void reduce_self(GDScriptParser::SelfNode *p_self); @@ -89,20 +91,25 @@ class GDScriptAnalyzer { void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op); void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); + void const_fold_array(GDScriptParser::ArrayNode *p_array); + void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary); + // Helpers. - GDScriptParser::DataType type_from_variant(const Variant &p_value); + GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; - GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name); + GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); - GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid); + GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); + GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); + void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); 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); + bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); #ifdef DEBUG_ENABLED bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); @@ -115,8 +122,6 @@ public: 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..0da99ccee3 --- /dev/null +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -0,0 +1,1597 @@ +/*************************************************************************/ +/* 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 = locals.size() + RESERVED_STACK; + locals.push_back(StackSlot(p_type.builtin_type)); + 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) { + return get_constant_pos(p_constant); +} + +uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) { + return get_name_map_pos(p_name); +} + +uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type) { + Variant::Type temp_type = Variant::NIL; + if (p_type.has_type) { + if (p_type.kind == GDScriptDataType::BUILTIN) { + switch (p_type.builtin_type) { + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::FLOAT: + case Variant::STRING: + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::RECT2: + case Variant::RECT2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::TRANSFORM2D: + case Variant::PLANE: + case Variant::QUAT: + case Variant::AABB: + case Variant::BASIS: + case Variant::TRANSFORM: + case Variant::COLOR: + case Variant::STRING_NAME: + case Variant::NODE_PATH: + case Variant::RID: + case Variant::OBJECT: + case Variant::CALLABLE: + case Variant::SIGNAL: + case Variant::DICTIONARY: + case Variant::ARRAY: + temp_type = p_type.builtin_type; + break; + 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: + case Variant::VARIANT_MAX: + // Packed arrays are reference counted, so we don't use the pool for them. + temp_type = Variant::NIL; + break; + } + } else { + temp_type = Variant::OBJECT; + } + } + + if (!temporaries_pool.has(temp_type)) { + temporaries_pool[temp_type] = List<int>(); + } + + List<int> &pool = temporaries_pool[temp_type]; + if (pool.is_empty()) { + StackSlot new_temp(temp_type); + int idx = temporaries.size(); + pool.push_back(idx); + temporaries.push_back(new_temp); + + // First time using this, so adjust to the proper type. + if (temp_type != Variant::NIL) { + Address addr(Address::TEMPORARY, idx, p_type); + write_type_adjust(addr, temp_type); + } + } + int slot = pool.front()->get(); + pool.pop_front(); + used_temporaries.push_back(slot); + return slot; +} + +void GDScriptByteCodeGenerator::pop_temporary() { + ERR_FAIL_COND(used_temporaries.is_empty()); + int slot_idx = used_temporaries.back()->get(); + const StackSlot &slot = temporaries[slot_idx]; + temporaries_pool[slot.type].push_back(slot_idx); + used_temporaries.pop_back(); +} + +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.reverse(); +} + +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 (!used_temporaries.is_empty()) { + ERR_PRINT("Non-zero temporary variables at end of function: " + itos(used_temporaries.size())); + } +#endif + append(GDScriptFunction::OPCODE_END, 0); + + for (int i = 0; i < temporaries.size(); i++) { + for (int j = 0; j < temporaries[i].bytecode_indices.size(); j++) { + opcodes.write[temporaries[i].bytecode_indices[j]] = (i + max_locals + RESERVED_STACK) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + } + } + + 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 (lambdas_map.size()) { + function->lambdas.resize(lambdas_map.size()); + function->_lambdas_ptr = function->lambdas.ptrw(); + function->_lambdas_count = lambdas_map.size(); + for (const Map<GDScriptFunction *, int>::Element *E = lambdas_map.front(); E; E = E->next()) { + function->lambdas.write[E->get()] = E->key(); + } + } else { + function->_lambdas_ptr = nullptr; + function->_lambdas_count = 0; + } + + if (debug_stack) { + function->stack_debug = stack_debug; + } + function->_stack_size = RESERVED_STACK + max_locals + temporaries.size(); + 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_type_adjust(const Address &p_target, Variant::Type p_new_type) { + switch (p_new_type) { + case Variant::BOOL: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_BOOL, 1); + break; + case Variant::INT: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_INT, 1); + break; + case Variant::FLOAT: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_FLOAT, 1); + break; + case Variant::STRING: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING, 1); + break; + case Variant::VECTOR2: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2, 1); + break; + case Variant::VECTOR2I: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2I, 1); + break; + case Variant::RECT2: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2, 1); + break; + case Variant::RECT2I: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2I, 1); + break; + case Variant::VECTOR3: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3, 1); + break; + case Variant::VECTOR3I: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3I, 1); + break; + case Variant::TRANSFORM2D: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM2D, 1); + break; + case Variant::PLANE: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PLANE, 1); + break; + case Variant::QUAT: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_QUAT, 1); + break; + case Variant::AABB: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_AABB, 1); + break; + case Variant::BASIS: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_BASIS, 1); + break; + case Variant::TRANSFORM: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM, 1); + break; + case Variant::COLOR: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_COLOR, 1); + break; + case Variant::STRING_NAME: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING_NAME, 1); + break; + case Variant::NODE_PATH: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_NODE_PATH, 1); + break; + case Variant::RID: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_RID, 1); + break; + case Variant::OBJECT: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_OBJECT, 1); + break; + case Variant::CALLABLE: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_CALLABLE, 1); + break; + case Variant::SIGNAL: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_SIGNAL, 1); + break; + case Variant::DICTIONARY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_DICTIONARY, 1); + break; + case Variant::ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_ARRAY, 1); + break; + case Variant::PACKED_BYTE_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, 1); + break; + case Variant::PACKED_INT32_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, 1); + break; + case Variant::PACKED_INT64_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, 1); + break; + case Variant::PACKED_FLOAT32_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, 1); + break; + case Variant::PACKED_FLOAT64_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, 1); + break; + case Variant::PACKED_STRING_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, 1); + break; + case Variant::PACKED_VECTOR2_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, 1); + break; + case Variant::PACKED_VECTOR3_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, 1); + break; + case Variant::PACKED_COLOR_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, 1); + break; + case Variant::NIL: + case Variant::VARIANT_MAX: + return; + } + append(p_target); +} + +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, 2); + 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: { + if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); + append(p_target); + append(p_source); + } else { + 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]; + Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; + class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << 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) | (GDScriptFunction::ADDR_TYPE_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_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 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_store_named_global(const Address &p_dst, const StringName &p_global) { + append(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL, 1); + append(p_dst); + append(p_global); +} + +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]; + Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; + append(GDScriptFunction::OPCODE_CAST_TO_NATIVE, 3); + index = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_type.script_type; + int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_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; + } + + if (p_target.mode == Address::TEMPORARY) { + Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); + Variant::Type temp_type = temporaries[p_target.address].type; + if (result_type != temp_type) { + write_type_adjust(p_target, result_type); + } + } + + 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_STACK << 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_SELF); + 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_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) { + append(GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size()); + for (int i = 0; i < p_captures.size(); i++) { + append(p_captures[i]); + } + + append(p_target); + append(p_captures.size()); + append(p_function); +} + +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_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_ARRAY, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + if (p_element_type.script_type) { + Variant script_type = Ref<Script>(p_element_type.script_type); + int addr = get_constant_pos(script_type); + addr |= GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS; + append(addr); + } else { + append(Address()); // null. + } + append(p_arguments.size()); + append(p_element_type.builtin_type); + append(p_element_type.native_type); +} + +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) { + if (!function->return_type.has_type || p_return_value.type.has_type) { + // Either the function is untyped or the return value is also typed. + + // If this is a typed function, then we need to check for potential conversions. + if (function->return_type.has_type) { + if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { + // Typed array. + const GDScriptDataType &element_type = function->return_type.get_container_element_type(); + + Variant script = function->return_type.script_type; + int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY, 2); + append(p_return_value); + append(script_idx); + append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(element_type.native_type); + } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { + // Add conversion. + append(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN, 1); + append(p_return_value); + append(function->return_type.builtin_type); + } else { + // Just assign. + append(GDScriptFunction::OPCODE_RETURN, 1); + append(p_return_value); + } + } else { + append(GDScriptFunction::OPCODE_RETURN, 1); + append(p_return_value); + } + } else { + switch (function->return_type.kind) { + case GDScriptDataType::BUILTIN: { + if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { + const GDScriptDataType &element_type = function->return_type.get_container_element_type(); + + Variant script = function->return_type.script_type; + int script_idx = get_constant_pos(script); + script_idx |= (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY, 2); + append(p_return_value); + append(script_idx); + append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(element_type.native_type); + } else { + append(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN, 1); + append(p_return_value); + append(function->return_type.builtin_type); + } + } break; + case GDScriptDataType::NATIVE: { + append(GDScriptFunction::OPCODE_RETURN_TYPED_NATIVE, 2); + append(p_return_value); + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[function->return_type.native_type]; + Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; + class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + append(class_idx); + } break; + case GDScriptDataType::GDSCRIPT: + case GDScriptDataType::SCRIPT: { + Variant script = function->return_type.script_type; + int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_RETURN_TYPED_SCRIPT, 2); + append(p_return_value); + append(script_idx); + } break; + default: { + ERR_PRINT("Compiler bug: unresolved return."); + + // Shouldn't get here, but fail-safe to a regular return; + append(GDScriptFunction::OPCODE_RETURN, 1); + append(p_return_value); + } break; + } + } +} + +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..c060476f39 --- /dev/null +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -0,0 +1,499 @@ +/*************************************************************************/ +/* 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 { + struct StackSlot { + Variant::Type type = Variant::NIL; + Vector<int> bytecode_indices; + + StackSlot() = default; + StackSlot(Variant::Type p_type) : + type(p_type) {} + }; + + const static int RESERVED_STACK = 3; // For self, class, and nil. + + 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; + + Vector<StackSlot> locals; + Vector<StackSlot> temporaries; + List<int> used_temporaries; + Map<Variant::Type, List<int>> temporaries_pool; + + List<GDScriptFunction::StackDebug> stack_debug; + List<Map<StringName, int>> block_identifier_stack; + Map<StringName, int> block_identifiers; + + int max_locals = 0; + int current_line = 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; + Map<GDScriptFunction *, int> lambdas_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) { + if (locals.size() > max_locals) { + max_locals = locals.size(); + } + 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(locals.size()); + 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() { + int 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 (!used_temporaries.is_empty()) { + ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(used_temporaries.size())); + } +#endif + locals.resize(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; + } + + int get_lambda_function_pos(GDScriptFunction *p_lambda_function) { + if (lambdas_map.has(p_lambda_function)) { + return lambdas_map[p_lambda_function]; + } + int pos = lambdas_map.size(); + lambdas_map[p_lambda_function] = pos; + return pos; + } + + 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_SELF; + case Address::CLASS: + return GDScriptFunction::ADDR_CLASS; + case Address::MEMBER: + return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); + case Address::CONSTANT: + return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + case Address::LOCAL_VARIABLE: + case Address::FUNCTION_PARAMETER: + return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + case Address::TEMPORARY: + temporaries.write[p_address.address].bytecode_indices.push_back(opcodes.size()); + return -1; + case Address::NIL: + return GDScriptFunction::ADDR_NIL; + } + 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 append(GDScriptFunction *p_lambda_function) { + opcodes.push_back(get_lambda_function_pos(p_lambda_function)); + } + + 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(const GDScriptDataType &p_type) 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_type_adjust(const Address &p_target, Variant::Type p_new_type) 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_store_named_global(const Address &p_dst, const StringName &p_global) 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_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) 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_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override; + virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_await(const Address &p_target, const Address &p_operand) override; + virtual void write_if(const Address &p_condition) override; + virtual void write_else() override; + virtual void write_endif() override; + virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override; + virtual void write_for_assignment(const Address &p_variable, const Address &p_list) override; + virtual void write_for() override; + virtual void write_endfor() override; + virtual void start_while_condition() override; + virtual void write_while(const Address &p_condition) override; + virtual void write_endwhile() override; + virtual void start_match() override; + virtual void start_match_branch() override; + virtual void end_match() override; + virtual void write_break() override; + virtual void write_continue() override; + virtual void write_continue_match() override; + virtual void write_breakpoint() override; + virtual void write_newline(int p_line) override; + virtual void write_return(const Address &p_return_value) override; + virtual void write_assert(const Address &p_test, const Address &p_message) override; + + virtual ~GDScriptByteCodeGenerator(); +}; + +#endif // GDSCRIPT_BYTE_CODEGEN diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index cdb14d6281..113d36be98 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.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,7 +31,7 @@ #include "gdscript_cache.h" #include "core/os/file_access.h" -#include "core/vector.h" +#include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" #include "gdscript_parser.h" @@ -108,26 +108,26 @@ GDScriptParserRef::~GDScriptParserRef() { if (analyzer != nullptr) { memdelete(analyzer); } - MutexLock(GDScriptCache::singleton->lock); + MutexLock lock(GDScriptCache::singleton->lock); GDScriptCache::singleton->parser_map.erase(path); } GDScriptCache *GDScriptCache::singleton = nullptr; void GDScriptCache::remove_script(const String &p_path) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); singleton->shallow_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path); } Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); Ref<GDScriptParserRef> ref; if (p_owner != String()) { singleton->dependencies[p_owner].insert(p_path); } if (singleton->parser_map.has(p_path)) { - ref = singleton->parser_map[p_path]; + ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]); } else { if (!FileAccess::exists(p_path)) { r_error = ERR_FILE_NOT_FOUND; @@ -137,8 +137,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP ref.instance(); ref->parser = parser; ref->path = p_path; - singleton->parser_map[p_path] = ref; - ref->unreference(); + singleton->parser_map[p_path] = ref.ptr(); } r_error = ref->raise_status(p_status); @@ -169,7 +168,7 @@ String GDScriptCache::get_source_code(const String &p_path) { } Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); if (p_owner != String()) { singleton->dependencies[p_owner].insert(p_path); } @@ -186,15 +185,12 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri script->set_script_path(p_path); script->load_source_code(p_path); - singleton->shallow_gdscript_cache[p_path] = script; - // The one in cache is not a hard reference: if the script dies somewhere else it's fine. - // Scripts remove themselves from cache when they die. - script->unreference(); + singleton->shallow_gdscript_cache[p_path] = script.ptr(); return script; } Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); if (p_owner != String()) { singleton->dependencies[p_owner].insert(p_path); @@ -217,7 +213,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro return script; } - singleton->full_gdscript_cache[p_path] = script; + singleton->full_gdscript_cache[p_path] = script.ptr(); singleton->shallow_gdscript_cache.erase(p_path); return script; @@ -226,7 +222,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro Error GDScriptCache::finish_compiling(const String &p_owner) { // Mark this as compiled. Ref<GDScript> script = get_shallow_script(p_owner); - singleton->full_gdscript_cache[p_owner] = script; + singleton->full_gdscript_cache[p_owner] = script.ptr(); singleton->shallow_gdscript_cache.erase(p_owner); Set<String> depends = singleton->dependencies[p_owner]; diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 770704d6eb..d1d2a2abbf 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.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,10 @@ #ifndef GDSCRIPT_CACHE_H #define GDSCRIPT_CACHE_H -#include "core/hash_map.h" +#include "core/object/reference.h" #include "core/os/mutex.h" -#include "core/reference.h" -#include "core/set.h" +#include "core/templates/hash_map.h" +#include "core/templates/set.h" #include "gdscript.h" class GDScriptAnalyzer; @@ -70,9 +70,9 @@ public: class GDScriptCache { // String key is full path. - HashMap<String, Ref<GDScriptParserRef>> parser_map; - HashMap<String, Ref<GDScript>> shallow_gdscript_cache; - HashMap<String, Ref<GDScript>> full_gdscript_cache; + HashMap<String, GDScriptParserRef *> parser_map; + HashMap<String, GDScript *> shallow_gdscript_cache; + HashMap<String, GDScript *> full_gdscript_cache; HashMap<String, Set<String>> dependencies; friend class GDScript; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h new file mode 100644 index 0000000000..ae9a8ede5e --- /dev/null +++ b/modules/gdscript/gdscript_codegen.h @@ -0,0 +1,160 @@ +/*************************************************************************/ +/* 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, + LOCAL_VARIABLE, + FUNCTION_PARAMETER, + TEMPORARY, + 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(const GDScriptDataType &p_type) = 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 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 write_type_adjust(const Address &p_target, Variant::Type p_new_type) = 0; + 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_store_named_global(const Address &p_dst, const StringName &p_global) = 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_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) = 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_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, 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_else() = 0; + virtual void write_endif() = 0; + virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0; + virtual void write_for_assignment(const Address &p_variable, const Address &p_list) = 0; + virtual void write_for() = 0; + virtual void write_endfor() = 0; + virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation. + virtual void write_while(const Address &p_condition) = 0; + virtual void write_endwhile() = 0; + virtual void start_match() = 0; + virtual void start_match_branch() = 0; + virtual void end_match() = 0; + virtual void write_break() = 0; + virtual void write_continue() = 0; + virtual void write_continue_match() = 0; + virtual void write_breakpoint() = 0; + virtual void write_newline(int p_line) = 0; + virtual void write_return(const Address &p_return_value) = 0; + virtual void write_assert(const Address &p_test, const Address &p_message) = 0; + + virtual ~GDScriptCodeGenerator() {} +}; + +#endif // GDSCRIPT_CODEGEN diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index e34d87f5cc..37ce8ae2cb 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,14 +31,16 @@ #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->is_static) { return false; } - if (codegen.stack_identifiers.has(p_name)) { + if (codegen.locals.has(p_name)) { return false; //shadowed } @@ -75,46 +77,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level) { - int src_address_a = _parse_expression(codegen, on->operand, p_stack_level); - if (src_address_a < 0) { - return false; - } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator - codegen.opcodes.push_back(op); //which operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_a); // argument 2 (repeated) - //codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_NIL); // argument 2 (unary only takes one parameter) - return true; -} - -bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { - int src_address_a = _parse_expression(codegen, p_left_operand, p_stack_level, false, p_initializer, p_index_addr); - if (src_address_a < 0) { - return false; - } - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - p_stack_level++; //uses stack for return, increase stack - } - - int src_address_b = _parse_expression(codegen, p_right_operand, p_stack_level, false, p_initializer); - if (src_address_b < 0) { - return false; - } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator - codegen.opcodes.push_back(op); //which operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - return true; -} - -bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { - return _create_binary_operator(codegen, on->left_operand, on->right_operand, op, p_stack_level, p_initializer, p_index_addr); -} - -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -136,14 +99,15 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; - result.script_type = p_datatype.script_type; + result.script_type_ref = Ref<Script>(p_datatype.script_type); + result.script_type = result.script_type_ref.ptr(); result.native_type = result.script_type->get_instance_base_type(); } break; case GDScriptParser::DataType::CLASS: { // Locate class by constructing the path to it and following that path GDScriptParser::ClassNode *class_type = p_datatype.class_type; if (class_type) { - if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.empty() && class_type->fqcn.begins_with(main_script->name))) { + 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) { @@ -162,280 +126,204 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D names.pop_back(); } result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = script; + result.script_type_ref = script; + result.script_type = result.script_type_ref.ptr(); result.native_type = script->get_instance_base_type(); } else { result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); + result.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); + result.script_type = result.script_type_ref.ptr(); result.native_type = p_datatype.native_type; } } } break; + case GDScriptParser::DataType::ENUM: 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(); } } - return result; -} - -int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr) { - Variant::Operator var_op = Variant::OP_MAX; - - switch (p_assignment->operation) { - case GDScriptParser::AssignmentNode::OP_ADDITION: - var_op = Variant::OP_ADD; - break; - case GDScriptParser::AssignmentNode::OP_SUBTRACTION: - var_op = Variant::OP_SUBTRACT; - break; - case GDScriptParser::AssignmentNode::OP_MULTIPLICATION: - var_op = Variant::OP_MULTIPLY; - break; - case GDScriptParser::AssignmentNode::OP_DIVISION: - var_op = Variant::OP_DIVIDE; - break; - case GDScriptParser::AssignmentNode::OP_MODULO: - var_op = Variant::OP_MODULE; - break; - case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_LEFT: - var_op = Variant::OP_SHIFT_LEFT; - break; - case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_RIGHT: - var_op = Variant::OP_SHIFT_RIGHT; - break; - case GDScriptParser::AssignmentNode::OP_BIT_AND: - var_op = Variant::OP_BIT_AND; - break; - case GDScriptParser::AssignmentNode::OP_BIT_OR: - var_op = Variant::OP_BIT_OR; - break; - case GDScriptParser::AssignmentNode::OP_BIT_XOR: - var_op = Variant::OP_BIT_XOR; - break; - case GDScriptParser::AssignmentNode::OP_NONE: { - //none - } break; - default: { - ERR_FAIL_V(-1); - } + if (p_datatype.has_container_element_type()) { + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type())); } - // bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN; - - if (var_op == Variant::OP_MAX) { - return _parse_expression(codegen, p_assignment->assigned_value, p_stack_level, false, false); + // 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>(); } - if (!_create_binary_operator(codegen, p_assignment->assignee, p_assignment->assigned_value, var_op, p_stack_level, false, p_index_addr)) { - return -1; - } - - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + return result; } -bool GDScriptCompiler::_generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type) { - if (p_datatype.has_type && p_value_type.is_variant()) { - // Typed assignment - switch (p_datatype.kind) { - case GDScriptDataType::BUILTIN: { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator - codegen.opcodes.push_back(p_datatype.builtin_type); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 - } break; - case GDScriptDataType::NATIVE: { - int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(p_datatype.native_type)) { - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_datatype.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) - } else { - // _set_error("Invalid native class type '" + String(p_datatype.native_type) + "'.", on->arguments[0]); - return false; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator - codegen.opcodes.push_back(class_idx); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - Variant script = p_datatype.script_type; - int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator - codegen.opcodes.push_back(idx); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 - } break; - default: { - ERR_PRINT("Compiler bug: unresolved assign."); - - // Shouldn't get here, but fail-safe to a regular assignment - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) - } +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; } - } else { - if (p_datatype.kind == GDScriptDataType::BUILTIN && p_value_type.kind == GDScriptParser::DataType::BUILTIN && p_datatype.builtin_type != p_value_type.builtin_type) { - // Need conversion. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator - codegen.opcodes.push_back(p_datatype.builtin_type); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 + StringName class_name; + if (p_arg_type.kind == GDScriptDataType::NATIVE) { + class_name = p_arg_type.native_type; } else { - // Either untyped assignment or already type-checked by the parser - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) + 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; + } +} + +static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { + if (p_method->get_argument_count() != p_arguments.size()) { + // ptrcall won't work with default arguments. + return false; + } + MethodInfo info; + ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info); + for (int i = 0; i < p_arguments.size(); i++) { + const PropertyInfo &prop = info.arguments[i]; + if (!_is_exact_type(prop, p_arguments[i].type)) { + return false; } } return true; } -int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { if (p_expression->is_constant) { - return codegen.get_constant_pos(p_expression->reduced_value); + return codegen.add_constant(p_expression->reduced_value); } + GDScriptCodeGenerator *gen = codegen.generator; + switch (p_expression->type) { - //should parse variable declaration and adjust stack accordingly... case GDScriptParser::Node::IDENTIFIER: { - //return identifier - //wait, identifier could be a local variable or something else... careful here, must reference properly - //as stack may be more interesting to work with - - //This could be made much simpler by just indexing "self", but done this way (with custom self-addressing modes) increases performance a lot. - + // Look for identifiers in current scope. const GDScriptParser::IdentifierNode *in = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); StringName identifier = in->name; - // TRY STACK! - if (!p_initializer && codegen.stack_identifiers.has(identifier)) { - int pos = codegen.stack_identifiers[identifier]; - return pos | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); + // Try function parameters. + if (codegen.parameters.has(identifier)) { + return codegen.parameters[identifier]; } - // TRY LOCAL CONSTANTS! - if (codegen.local_named_constants.has(identifier)) { - return codegen.local_named_constants[identifier] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + // Try local variables and constants. + if (!p_initializer && codegen.locals.has(identifier)) { + return codegen.locals[identifier]; } - // TRY CLASS MEMBER + // Try class members. if (_is_class_member_property(codegen, identifier)) { - //get property - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_MEMBER); // perform operator - codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + // Get property. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Could get the type of the class member here. + gen->write_get_member(temp, identifier); + return temp; } - //TRY MEMBERS! + // Try members. if (!codegen.function_node || !codegen.function_node->is_static) { - // TRY MEMBER VARIABLES! - //static function + // Try member variables. if (codegen.script->member_indices.has(identifier)) { if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { // Perform getter. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); - codegen.opcodes.push_back(0); // Argument count. - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self). - codegen.opcodes.push_back(codegen.get_name_map_pos(codegen.script->member_indices[identifier].getter)); // Method name. - // Destination. - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); + Vector<GDScriptCodeGenerator::Address> args; // No argument needed. + gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); + return temp; } else { - // No getter or inside getter: direct member access. + // No getter or inside getter: direct member access., int idx = codegen.script->member_indices[identifier].index; - return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root) + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier)); } } } - //TRY CLASS CONSTANTS - - GDScript *owner = codegen.script; - while (owner) { - GDScript *scr = owner; - GDScriptNativeClass *nc = nullptr; - while (scr) { - if (scr->constants.has(identifier)) { - //int idx=scr->constants[identifier]; - int idx = codegen.get_name_map_pos(identifier); - return idx | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) - } - if (scr->native.is_valid()) { - nc = scr->native.ptr(); + // Try class constants. + { + GDScript *owner = codegen.script; + while (owner) { + GDScript *scr = owner; + GDScriptNativeClass *nc = nullptr; + while (scr) { + if (scr->constants.has(identifier)) { + return codegen.add_constant(scr->constants[identifier]); // TODO: Get type here. + } + if (scr->native.is_valid()) { + nc = scr->native.ptr(); + } + scr = scr->_base; } - scr = scr->_base; - } - - // CLASS C++ Integer Constant - if (nc) { - bool success = false; - int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); - if (success) { - Variant key = constant; - int idx; + // Class C++ integer constant. + if (nc) { + bool success = false; + int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); + if (success) { + return codegen.add_constant(constant); + } + } - if (!codegen.constant_map.has(key)) { - idx = codegen.constant_map.size(); - codegen.constant_map[key] = idx; + owner = owner->_owner; + } + } - } else { - idx = codegen.constant_map[key]; - } + // 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); - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + gen->write_get_named(temp, identifier, self); + return temp; } } - owner = owner->_owner; - } + // Try in native base. + GDScript *scr = codegen.script; + GDScriptNativeClass *nc = nullptr; + while (scr) { + if (scr->native.is_valid()) { + nc = scr->native.ptr(); + } + scr = scr->_base; + } - // 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) { + if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) { // Get like it was a property. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_NAMED); // perform operator - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Self. - codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. + GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); + + gen->write_get_named(temp, identifier, self); + return temp; } } if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return codegen.add_constant(global); // TODO: Get type. } - /* TRY GLOBAL CLASSES */ - + // Try global classes. if (ScriptServer::is_global_class(identifier)) { const GDScriptParser::ClassNode *class_node = codegen.class_node; while (class_node->outer) { @@ -450,356 +338,257 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); if (res.is_null()) { _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } } - Variant key = res; - int idx; - - if (!codegen.constant_map.has(key)) { - idx = codegen.constant_map.size(); - codegen.constant_map[key] = idx; - - } else { - idx = codegen.constant_map[key]; - } - - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + return codegen.add_constant(res); } #ifdef TOOLS_ENABLED if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { - int idx = codegen.named_globals.find(identifier); - if (idx == -1) { - idx = codegen.named_globals.size(); - codegen.named_globals.push_back(identifier); - } - return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + GDScriptCodeGenerator::Address global = codegen.add_temporary(); // TODO: Get type. + gen->write_store_named_global(global, identifier); + return global; } #endif - //not found, error - + // Not found, error. _set_error("Identifier not found: " + String(identifier), p_expression); - - return -1; - + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } break; case GDScriptParser::Node::LITERAL: { - //return constant + // Return constant. const GDScriptParser::LiteralNode *cn = static_cast<const GDScriptParser::LiteralNode *>(p_expression); - int idx; - - if (!codegen.constant_map.has(cn->value)) { - idx = codegen.constant_map.size(); - codegen.constant_map[cn->value] = idx; - - } else { - idx = codegen.constant_map[cn->value]; - } - - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) - + return codegen.add_constant(cn->value); } break; case GDScriptParser::Node::SELF: { //return constant if (codegen.function_node && codegen.function_node->is_static) { _set_error("'self' not present in static function!", p_expression); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } - return (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF); } break; case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); - Vector<int> values; + Vector<GDScriptCodeGenerator::Address> values; - int slevel = p_stack_level; + // Create the result temporary first since it's the last to be killed. + GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); + 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(val); + } - values.push_back(ret); + if (array_type.has_container_element_type()) { + gen->write_construct_typed_array(result, array_type.get_container_element_type(), values); + } else { + gen->write_construct_array(result, values); } - 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]); + if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; - + return result; } break; case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); - Vector<int> elements; + Vector<GDScriptCodeGenerator::Address> elements; - int slevel = p_stack_level; + // Create the result temporary first since it's the last to be killed. + GDScriptDataType dict_type; + dict_type.has_type = true; + dict_type.kind = GDScriptDataType::BUILTIN; + dict_type.builtin_type = Variant::DICTIONARY; + GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type); for (int i = 0; i < dn->elements.size(); i++) { // Key. - int ret = -1; + GDScriptCodeGenerator::Address element; switch (dn->style) { case GDScriptParser::DictionaryNode::PYTHON_DICT: // Python-style: key is any expression. - ret = _parse_expression(codegen, dn->elements[i].key, slevel); - if (ret < 0) { - return ret; - } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + element = _parse_expression(codegen, r_error, dn->elements[i].key); + if (r_error) { + return GDScriptCodeGenerator::Address(); } break; case GDScriptParser::DictionaryNode::LUA_TABLE: - // Lua-style: key is an identifier interpreted as string. - String key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; - ret = codegen.get_constant_pos(key); + // Lua-style: key is an identifier interpreted as StringName. + StringName key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; + element = codegen.add_constant(key); break; } - elements.push_back(ret); + elements.push_back(element); - ret = _parse_expression(codegen, dn->elements[i].value, slevel); - if (ret < 0) { - return ret; - } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + element = _parse_expression(codegen, r_error, dn->elements[i].value); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - elements.push_back(ret); + elements.push_back(element); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY); - codegen.opcodes.push_back(dn->elements.size()); + gen->write_construct_dictionary(result, elements); + for (int i = 0; i < elements.size(); i++) { - codegen.opcodes.push_back(elements[i]); + if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; - + return result; } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); - int slevel = p_stack_level; - int src_addr = _parse_expression(codegen, cn->operand, slevel); - if (src_addr < 0) { - return src_addr; - } - if (src_addr & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + // Create temporary for result first since it will be deleted last. + GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type); - GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); + GDScriptCodeGenerator::Address source = _parse_expression(codegen, r_error, cn->operand); - switch (cast_type.kind) { - case GDScriptDataType::BUILTIN: { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); - codegen.opcodes.push_back(cast_type.builtin_type); - } break; - case GDScriptDataType::NATIVE: { - int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(cast_type.native_type)) { - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cast_type.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) - } else { - _set_error("Invalid native class type '" + String(cast_type.native_type) + "'.", cn); - return -1; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator - codegen.opcodes.push_back(class_idx); // variable type - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - Variant script = cast_type.script_type; - int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) + gen->write_cast(result, source, cast_type); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator - codegen.opcodes.push_back(idx); // variable type - } break; - default: { - _set_error("Parser bug: unresolved data type.", cn); - return -1; - } + if (source.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(src_addr); // source address - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; - + return result; } break; - //hell breaks loose - -#define OPERATOR_RETURN \ - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); \ - codegen.opcodes.push_back(dst_addr); \ - codegen.alloc_stack(p_stack_level); \ - return dst_addr - case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != Variant::VARIANT_MAX) { - //construct a basic type + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); + GDScriptCodeGenerator::Address result = codegen.add_temporary(type); - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 0; i < call->arguments.size(); i++) { - int ret = _parse_expression(codegen, call->arguments[i], slevel); - if (ret < 0) { - return ret; - } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); - } - - //push call bytecode - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT); // basic type constructor - codegen.opcodes.push_back(vtype); //instance - codegen.opcodes.push_back(arguments.size()); //argument count - codegen.alloc_call(arguments.size()); - for (int i = 0; i < arguments.size(); i++) { - codegen.opcodes.push_back(arguments[i]); //arguments - } - - } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != GDScriptFunctions::FUNC_MAX) { - //built in function - - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 0; i < call->arguments.size(); i++) { - int ret = _parse_expression(codegen, call->arguments[i], slevel); - if (ret < 0) { - return ret; - } - - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - - arguments.push_back(ret); + 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); + } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name)); - codegen.opcodes.push_back(arguments.size()); - codegen.alloc_call(arguments.size()); - for (int i = 0; i < arguments.size(); i++) { - codegen.opcodes.push_back(arguments[i]); - } + if (!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 - + // Regular function. const GDScriptParser::ExpressionNode *callee = call->callee; - Vector<int> arguments; - int slevel = p_stack_level; - - // TODO: Use callables when possible if needed. - int ret = -1; - int super_address = -1; if (call->is_super) { // Super call. - if (call->callee == nullptr) { - // Implicit super function call. - super_address = codegen.get_name_map_pos(codegen.function_node->identifier->name); - } else { - super_address = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - } + gen->write_super_call(result, call->function_name, arguments); } else { if (callee->type == GDScriptParser::Node::IDENTIFIER) { // Self function call. - if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") { - ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS); + if (ClassDB::has_method(codegen.script->native->get_name(), call->function_name)) { + // Native method, use faster path. + GDScriptCodeGenerator::Address self; + self.mode = GDScriptCodeGenerator::Address::SELF; + MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name); + + if (_have_exact_arguments(method, arguments)) { + // Exact arguments, use ptrcall. + gen->write_call_ptrcall(result, self, method, arguments); + } else { + // Not exact arguments, but still can use method bind call. + gen->write_call_method_bind(result, self, method, arguments); + } + } else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") { + GDScriptCodeGenerator::Address self; + self.mode = GDScriptCodeGenerator::Address::CLASS; + if (within_await) { + gen->write_call_async(result, self, call->function_name, arguments); + } else { + gen->write_call(result, self, call->function_name, arguments); + } } else { - ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + if (within_await) { + gen->write_call_self_async(result, call->function_name, arguments); + } else { + gen->write_call_self(result, call->function_name, arguments); + } } - arguments.push_back(ret); - ret = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - arguments.push_back(ret); } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); if (subscript->is_attribute) { - ret = _parse_expression(codegen, subscript->base, slevel); - if (ret < 0) { - return ret; + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + if (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 ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - arguments.push_back(ret); - arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name)); } else { _set_error("Cannot call something that isn't a function.", call->callee); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } } else { - _set_error("Cannot call something that isn't a function.", call->callee); - return -1; - } - } - - for (int i = 0; i < call->arguments.size(); i++) { - ret = _parse_expression(codegen, call->arguments[i], slevel); - if (ret < 0) { - return ret; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); - } - - int opcode = GDScriptFunction::OPCODE_CALL_RETURN; - if (call->is_super) { - opcode = GDScriptFunction::OPCODE_CALL_SELF_BASE; - } else if (within_await) { - opcode = GDScriptFunction::OPCODE_CALL_ASYNC; - } else if (p_root) { - opcode = GDScriptFunction::OPCODE_CALL; } + } - codegen.opcodes.push_back(opcode); // perform operator - if (call->is_super) { - codegen.opcodes.push_back(super_address); - } - codegen.opcodes.push_back(call->arguments.size()); - codegen.alloc_call(call->arguments.size()); - for (int i = 0; i < arguments.size(); i++) { - codegen.opcodes.push_back(arguments[i]); + for (int i = 0; i < arguments.size(); i++) { + if (arguments[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } } - OPERATOR_RETURN; + return result; } break; case GDScriptParser::Node::GET_NODE: { const GDScriptParser::GetNodeNode *get_node = static_cast<const GDScriptParser::GetNodeNode *>(p_expression); @@ -816,59 +605,55 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: } } - int arg_address = codegen.get_constant_pos(NodePath(node_name)); + Vector<GDScriptCodeGenerator::Address> args; + args.push_back(codegen.add_constant(NodePath(node_name))); + + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype())); - codegen.opcodes.push_back(p_root ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); - codegen.opcodes.push_back(1); // number of arguments. - codegen.alloc_call(1); - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // self. - codegen.opcodes.push_back(codegen.get_name_map_pos("get_node")); // function. - codegen.opcodes.push_back(arg_address); // argument (NodePath). - OPERATOR_RETURN; + MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); + gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); + + return result; } break; case GDScriptParser::Node::PRELOAD: { const GDScriptParser::PreloadNode *preload = static_cast<const GDScriptParser::PreloadNode *>(p_expression); // Add resource as constant. - return codegen.get_constant_pos(preload->resource); + return codegen.add_constant(preload->resource); } break; case GDScriptParser::Node::AWAIT: { const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); - int slevel = p_stack_level; + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype())); within_await = true; - int argument = _parse_expression(codegen, await->to_await, slevel); + GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await); within_await = false; - if (argument < 0) { - return argument; + if (r_error) { + return GDScriptCodeGenerator::Address(); } - if ((argument >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + + gen->write_await(result, argument); + + if (argument.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - //push call bytecode - codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT); - codegen.opcodes.push_back(argument); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT_RESUME); - //next will be where to place the result :) - OPERATOR_RETURN; + return result; } break; - - //indexing operator + // Indexing operator. case GDScriptParser::Node::SUBSCRIPT: { - int slevel = p_stack_level; - const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); - int from = _parse_expression(codegen, subscript->base, slevel); - if (from < 0) { - return from; + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); } bool named = subscript->is_attribute; - int index; - if (p_index_addr != 0) { + StringName name; + GDScriptCodeGenerator::Address index; + if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) { index = p_index_addr; } else if (subscript->is_attribute) { if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { @@ -879,306 +664,183 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: if (MI && MI->get().getter == codegen.function_name) { String n = identifier->name; _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } #endif if (MI && MI->get().getter == "") { - // Faster than indexing self (as if no self. had been used) - return (MI->get().index) | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); + // Remove result temp as we don't need it. + gen->pop_temporary(); + // Faster than indexing self (as if no self. had been used). + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->get().index, _gdtype_from_datatype(subscript->get_datatype())); } } - index = codegen.get_name_map_pos(subscript->attribute->name); - + name = subscript->attribute->name; + named = true; } else { - if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value.get_type() == Variant::STRING) { - //also, somehow, named (speed up anyway) - StringName name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value; - index = codegen.get_name_map_pos(name); + if (subscript->index->is_constant && subscript->index->reduced_value.get_type() == Variant::STRING_NAME) { + // Also, somehow, named (speed up anyway). + name = subscript->index->reduced_value; named = true; - } else { - //regular indexing - if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } - - index = _parse_expression(codegen, subscript->index, slevel); - if (index < 0) { - return index; + // Regular indexing. + index = _parse_expression(codegen, r_error, subscript->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); } } } - codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); // perform operator - codegen.opcodes.push_back(from); // argument 1 - codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter) - OPERATOR_RETURN; + if (named) { + gen->write_get_named(result, name, base); + } else { + gen->write_get(result, index, base); + } + + if (index.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + + return result; } break; case GDScriptParser::Node::UNARY_OPERATOR: { - //unary operators const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - switch (unary->operation) { - case GDScriptParser::UnaryOpNode::OP_NEGATIVE: { - if (!_create_unary_operator(codegen, unary, Variant::OP_NEGATE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::UnaryOpNode::OP_POSITIVE: { - if (!_create_unary_operator(codegen, unary, Variant::OP_POSITIVE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::UnaryOpNode::OP_LOGIC_NOT: { - if (!_create_unary_operator(codegen, unary, Variant::OP_NOT, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::UnaryOpNode::OP_COMPLEMENT: { - if (!_create_unary_operator(codegen, unary, Variant::OP_BIT_NEGATE, p_stack_level)) { - return -1; - } - } break; + + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype())); + + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_unary_operator(result, unary->variant_op, operand); + + if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - OPERATOR_RETURN; + + return result; } case GDScriptParser::Node::BINARY_OPERATOR: { - //binary operators (in precedence order) const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype())); + switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { - // AND operator with early out on failure + // AND operator with early out on failure. + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + gen->write_and_left_operand(left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); + gen->write_and_right_operand(right_operand); + + gen->write_end_and(result); - int res = _parse_expression(codegen, binary->left_operand, p_stack_level); - if (res < 0) { - return res; + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(res); - int jump_fail_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - res = _parse_expression(codegen, binary->right_operand, p_stack_level); - if (res < 0) { - return res; + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(res); - int jump_fail_pos2 = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); - codegen.opcodes.write[jump_fail_pos2] = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - } break; case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: { - // OR operator with early out on success + // OR operator with early out on success. + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + gen->write_or_left_operand(left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); + gen->write_or_right_operand(right_operand); + + gen->write_end_or(result); - int res = _parse_expression(codegen, binary->left_operand, p_stack_level); - if (res < 0) { - return res; + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); - codegen.opcodes.push_back(res); - int jump_success_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - res = _parse_expression(codegen, binary->right_operand, p_stack_level); - if (res < 0) { - return res; + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); - codegen.opcodes.push_back(res); - int jump_success_pos2 = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes.write[jump_success_pos] = codegen.opcodes.size(); - codegen.opcodes.write[jump_success_pos2] = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - } break; case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: { - int slevel = p_stack_level; - - int src_address_a = _parse_expression(codegen, binary->left_operand, slevel); - if (src_address_a < 0) { - return -1; - } - - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; //uses stack for return, increase stack - } + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand); - int src_address_b = -1; - bool builtin = false; if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name) != Variant::VARIANT_MAX) { - // `is` with builtin type - builtin = true; - src_address_b = (int)GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name); + // `is` with builtin type) + Variant::Type type = GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name); + gen->write_type_test_builtin(result, operand, type); } else { - src_address_b = _parse_expression(codegen, binary->right_operand, slevel); - if (src_address_b < 0) { - return -1; + GDScriptCodeGenerator::Address type = _parse_expression(codegen, r_error, binary->right_operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_type_test(result, operand, type); + if (type.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } } - codegen.opcodes.push_back(builtin ? GDScriptFunction::OPCODE_IS_BUILTIN : GDScriptFunction::OPCODE_EXTENDS_TEST); // perform operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - - } break; - case GDScriptParser::BinaryOpNode::OP_CONTENT_TEST: { - if (!_create_binary_operator(codegen, binary, Variant::OP_IN, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_NOT_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_NOT_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_LESS: { - if (!_create_binary_operator(codegen, binary, Variant::OP_LESS, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_LESS_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_LESS_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_GREATER: { - if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_GREATER_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_ADDITION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_ADD, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_SUBTRACTION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_SUBTRACT, p_stack_level)) { - return -1; + if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } } break; - case GDScriptParser::BinaryOpNode::OP_MULTIPLICATION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_MULTIPLY, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_DIVISION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_DIVIDE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_MODULO: { - if (!_create_binary_operator(codegen, binary, Variant::OP_MODULE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_AND: { - if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_AND, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_OR: { - if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_OR, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_XOR: { - if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_XOR, p_stack_level)) { - return -1; + 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); + + gen->write_binary_operator(result, binary->variant_op, left_operand, right_operand); + + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - } break; - //shift - case GDScriptParser::BinaryOpNode::OP_BIT_LEFT_SHIFT: { - if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_LEFT, p_stack_level)) { - return -1; + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_RIGHT_SHIFT: { - if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_RIGHT, p_stack_level)) { - return -1; - } - } break; + } } - OPERATOR_RETURN; + return result; } break; - // ternary operators case GDScriptParser::Node::TERNARY_OPERATOR: { - // x IF a ELSE y operator with early out on failure - + // x IF a ELSE y operator with early out on failure. const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression); - int res = _parse_expression(codegen, ternary->condition, p_stack_level); - if (res < 0) { - return res; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(res); - int jump_fail_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype())); - res = _parse_expression(codegen, ternary->true_expr, p_stack_level); - if (res < 0) { - return res; - } + gen->write_start_ternary(result); - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(res); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - int jump_past_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, r_error, ternary->condition); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_condition(condition); - codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); - res = _parse_expression(codegen, ternary->false_expr, p_stack_level); - if (res < 0) { - return res; + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(res); + GDScriptCodeGenerator::Address true_expr = _parse_expression(codegen, r_error, ternary->true_expr); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_true_expr(true_expr); + if (true_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size(); + GDScriptCodeGenerator::Address false_expr = _parse_expression(codegen, r_error, ternary->false_expr); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_false_expr(false_expr); + if (false_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + gen->write_end_ternary(); + return result; } break; - //assignment operators case GDScriptParser::Node::ASSIGNMENT: { const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression); @@ -1186,20 +848,16 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: // SET (chained) MODE! const GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(assignment->assignee); #ifdef DEBUG_ENABLED - if (subscript->is_attribute) { - if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { - const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name); - if (MI && MI->get().setter == codegen.function_name) { - String n = subscript->attribute->name; - _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript); - return -1; - } + if (subscript->is_attribute && subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { + const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name); + if (MI && MI->get().setter == codegen.function_name) { + String n = subscript->attribute->name; + _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } } #endif - - int slevel = p_stack_level; - /* Find chain of sets */ StringName assign_property; @@ -1207,13 +865,12 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: List<const GDScriptParser::SubscriptNode *> chain; { - //create get/set chain + // Create get/set chain. const GDScriptParser::SubscriptNode *n = subscript; while (true) { chain.push_back(n); - if (n->base->type != GDScriptParser::Node::SUBSCRIPT) { - //check for a built-in property + // Check for a built-in property. if (n->base->type == GDScriptParser::Node::IDENTIFIER) { GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base); if (_is_class_member_property(codegen, identifier->name)) { @@ -1228,366 +885,424 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: /* Chain of gets */ - //get at (potential) root stack pos, so it can be returned - int prev_pos = _parse_expression(codegen, chain.back()->get()->base, slevel); - if (prev_pos < 0) { - return prev_pos; + // Get at (potential) root stack pos, so it can be returned. + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - int retval = prev_pos; - if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + GDScriptCodeGenerator::Address prev_base = base; - Vector<int> setchain; + struct ChainInfo { + bool is_named = false; + GDScriptCodeGenerator::Address base; + GDScriptCodeGenerator::Address key; + StringName name; + }; - if (assign_property != StringName()) { - // recover and assign at the end, this allows stuff like - // position.x+=2.0 - // in Node2D - setchain.push_back(prev_pos); - setchain.push_back(codegen.get_name_map_pos(assign_property)); - setchain.push_back(GDScriptFunction::OPCODE_SET_MEMBER); - } + List<ChainInfo> set_chain; for (List<const GDScriptParser::SubscriptNode *>::Element *E = chain.back(); E; E = E->prev()) { - if (E == chain.front()) { //ignore first + if (E == chain.front()) { + // Skip the main subscript, since we'll assign to that. break; } - const GDScriptParser::SubscriptNode *subscript_elem = E->get(); - int key_idx; + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype())); + GDScriptCodeGenerator::Address key; + StringName name; if (subscript_elem->is_attribute) { - key_idx = codegen.get_name_map_pos(subscript_elem->attribute->name); - //printf("named key %x\n",key_idx); - + name = subscript_elem->attribute->name; + gen->write_get_named(value, name, prev_base); } else { - if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) { - slevel++; - codegen.alloc_stack(slevel); + key = _parse_expression(codegen, r_error, subscript_elem->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - - GDScriptParser::ExpressionNode *key = subscript_elem->index; - key_idx = _parse_expression(codegen, key, slevel); - //printf("expr key %x\n",key_idx); - - //stack was raised here if retval was stack but.. + gen->write_get(value, key, prev_base); } - if (key_idx < 0) { //error - return key_idx; - } - - codegen.opcodes.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(prev_pos); - codegen.opcodes.push_back(key_idx); - slevel++; - codegen.alloc_stack(slevel); - int dst_pos = (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | slevel; - - codegen.opcodes.push_back(dst_pos); - - //add in reverse order, since it will be reverted - - setchain.push_back(dst_pos); - setchain.push_back(key_idx); - setchain.push_back(prev_pos); - setchain.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); - - prev_pos = dst_pos; + // Store base and key for setting it back later. + set_chain.push_front({ subscript_elem->is_attribute, prev_base, key, name }); // Push to front to invert the list. + prev_base = value; } - setchain.invert(); - - int set_index; - + // Get value to assign. + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + // Get the key if needed. + GDScriptCodeGenerator::Address key; + StringName name; if (subscript->is_attribute) { - set_index = codegen.get_name_map_pos(subscript->attribute->name); + name = subscript->attribute->name; } else { - set_index = _parse_expression(codegen, subscript->index, slevel + 1); + key = _parse_expression(codegen, r_error, subscript->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } } - if (set_index < 0) { //error - return set_index; + // Perform operator if any. + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address value = codegen.add_temporary(); + if (subscript->is_attribute) { + gen->write_get_named(value, name, prev_base); + } else { + gen->write_get(value, key, prev_base); + } + gen->write_binary_operator(value, assignment->variant_op, value, assigned); + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + assigned = value; } - if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); + // Perform assignment. + if (subscript->is_attribute) { + gen->write_set_named(prev_base, name, assigned); + } else { + gen->write_set(prev_base, key, assigned); } - - int set_value = _parse_assign_right_expression(codegen, assignment, slevel + 1, subscript->is_attribute ? 0 : set_index); - if (set_value < 0) { //error - return set_value; + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(subscript->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); - codegen.opcodes.push_back(prev_pos); - codegen.opcodes.push_back(set_index); - codegen.opcodes.push_back(set_value); + assigned = prev_base; - for (int i = 0; i < setchain.size(); i++) { - codegen.opcodes.push_back(setchain[i]); + // Set back the values into their bases. + for (List<ChainInfo>::Element *E = set_chain.front(); E; E = E->next()) { + const ChainInfo &info = E->get(); + if (!info.is_named) { + gen->write_set(info.base, info.key, assigned); + if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + gen->write_set_named(info.base, info.name, assigned); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + assigned = info.base; } - return retval; + // If this is a local member, also assign to it. + // This allow things like: position.x += 2.0 + if (assign_property != StringName()) { + gen->write_set_member(assigned, assign_property); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name)) { - //assignment to member property - - int slevel = p_stack_level; - - int src_address = _parse_assign_right_expression(codegen, assignment, slevel); - if (src_address < 0) { - return -1; + // Assignment to member property. + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + if (r_error) { + return GDScriptCodeGenerator::Address(); } + GDScriptCodeGenerator::Address assign_temp = assigned; StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER); - codegen.opcodes.push_back(codegen.get_name_map_pos(name)); - codegen.opcodes.push_back(src_address); + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address member = codegen.add_temporary(); + gen->write_get_member(member, name); + gen->write_binary_operator(assigned, assignment->variant_op, member, assigned); + gen->pop_temporary(); + } - return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; - } else { - //REGULAR ASSIGNMENT MODE!! + gen->write_set_member(assigned, name); - int slevel = p_stack_level; - int dst_address_a = -1; + if (assign_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + // Regular assignment. + GDScriptCodeGenerator::Address target; bool has_setter = false; bool is_in_setter = false; StringName setter_function; if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.stack_identifiers.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { setter_function = codegen.script->member_indices[var_name].setter; if (setter_function != StringName()) { has_setter = true; is_in_setter = setter_function == codegen.function_name; - dst_address_a = codegen.script->member_indices[var_name].index; + target.mode = GDScriptCodeGenerator::Address::MEMBER; + target.address = codegen.script->member_indices[var_name].index; } } } if (has_setter) { - if (is_in_setter) { - // Use direct member access. - dst_address_a |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; - } else { + if (!is_in_setter) { // Store stack slot for the temp value. - dst_address_a = slevel++; - codegen.alloc_stack(slevel); - dst_address_a |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + target = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype())); } } else { - dst_address_a = _parse_expression(codegen, assignment->assignee, slevel); - if (dst_address_a < 0) { - return -1; + target = _parse_expression(codegen, r_error, assignment->assignee); + if (r_error) { + return GDScriptCodeGenerator::Address(); } + } - if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + GDScriptCodeGenerator::Address op_result; + if (r_error) { + return GDScriptCodeGenerator::Address(); } - int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel); - if (src_address_b < 0) { - return -1; + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + // Perform operation. + op_result = codegen.add_temporary(); + gen->write_binary_operator(op_result, assignment->variant_op, target, assigned); + } else { + op_result = assigned; + assigned = GDScriptCodeGenerator::Address(); } GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); if (has_setter && !is_in_setter) { // Call setter. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL); - codegen.opcodes.push_back(1); // Argument count. - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self). - codegen.opcodes.push_back(codegen.get_name_map_pos(setter_function)); // Method name. - codegen.opcodes.push_back(dst_address_a); // Argument. - codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here). - codegen.alloc_call(1); - } else if (!_generate_typed_assign(codegen, src_address_b, dst_address_a, assign_type, assignment->assigned_value->get_datatype())) { - return -1; + Vector<GDScriptCodeGenerator::Address> args; + args.push_back(op_result); + gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args); + } else { + // Just assign. + gen->write_assign(target, op_result); + } + + 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; + case GDScriptParser::Node::LAMBDA: { + const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype())); + + Vector<GDScriptCodeGenerator::Address> captures; + captures.resize(lambda->captures.size()); + for (int i = 0; i < lambda->captures.size(); i++) { + captures.write[i] = _parse_expression(codegen, r_error, lambda->captures[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + } - return dst_address_a; //if anything, returns wathever was assigned or correct stack position + GDScriptFunction *function = _parse_function(r_error, codegen.script, codegen.class_node, lambda->function, false, true); + if (r_error) { + return GDScriptCodeGenerator::Address(); } + + gen->write_lambda(result, function, captures); + + for (int i = 0; i < captures.size(); i++) { + if (captures[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + + return result; } break; -#undef OPERATOR_RETURN - //TYPE_TYPE, default: { - ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code + ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code. } break; } } -Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address) { - // TODO: Many "repeated" code here that could be abstracted. This compiler is going away when new VM arrives though, so... +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested) { switch (p_pattern->pattern_type) { case GDScriptParser::PatternNode::PT_LITERAL: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Get literal type into constant map. - int literal_type_addr = -1; - if (!codegen.constant_map.has((int)p_pattern->literal->value.get_type())) { - literal_type_addr = codegen.constant_map.size(); - codegen.constant_map[(int)p_pattern->literal->value.get_type()] = literal_type_addr; + GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant((int)p_pattern->literal->value.get_type()); - } else { - literal_type_addr = codegen.constant_map[(int)p_pattern->literal->value.get_type()]; - } - literal_type_addr |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; + // Equality is always a boolean. + GDScriptDataType equality_type; + equality_type.has_type = true; + equality_type.kind = GDScriptDataType::BUILTIN; + equality_type.builtin_type = Variant::BOOL; // Check type equality. - int equality_addr = p_stack_level++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(p_stack_level); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(literal_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + codegen.generator->write_and_left_operand(type_equality_addr); // Get literal. - int literal_addr = _parse_expression(codegen, p_pattern->literal, p_stack_level); + GDScriptCodeGenerator::Address literal_addr = _parse_expression(codegen, r_error, p_pattern->literal); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } // Check value equality. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_value_addr); - codegen.opcodes.push_back(literal_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if doesn't match. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. - - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address equality_addr = codegen.add_temporary(equality_type); + codegen.generator->write_binary_operator(equality_addr, Variant::OP_EQUAL, p_value_addr, literal_addr); + codegen.generator->write_and_right_operand(equality_addr); + + // AND both together (reuse temporary location). + codegen.generator->write_end_and(type_equality_addr); + + codegen.generator->pop_temporary(); // Remove equality_addr from stack. + + if (literal_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(type_equality_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(type_equality_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, type_equality_addr); + } + codegen.generator->pop_temporary(); // Remove type_equality_addr. + + return p_previous_test; } break; case GDScriptParser::PatternNode::PT_EXPRESSION: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Create the result temps first since it's the last to go away. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary(); + // Evaluate expression. - int expr_addr = _parse_expression(codegen, p_pattern->expression, p_stack_level); - if ((expr_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - p_stack_level++; - codegen.alloc_stack(p_stack_level); + GDScriptCodeGenerator::Address expr_addr; + expr_addr = _parse_expression(codegen, r_error, p_pattern->expression); + if (r_error) { + return GDScriptCodeGenerator::Address(); } // Evaluate expression type. - int expr_type_addr = p_stack_level++; - expr_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(p_stack_level); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(expr_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(expr_type_addr); // Address to result. + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(expr_addr); + codegen.generator->write_call_utility(result_addr, "typeof", typeof_args); // Check type equality. - int equality_addr = p_stack_level++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(p_stack_level); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(expr_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr); + codegen.generator->write_and_left_operand(result_addr); // Check value equality. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_value_addr); - codegen.opcodes.push_back(expr_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if doesn't match. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. - - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. - } break; - case GDScriptParser::PatternNode::PT_BIND: { - // Create new stack variable. - int bind_addr = p_stack_level | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); - codegen.add_stack_identifier(p_pattern->bind->name, p_stack_level++); - codegen.alloc_stack(p_stack_level); - r_bound_variables++; + codegen.generator->write_binary_operator(equality_test_addr, Variant::OP_EQUAL, p_value_addr, expr_addr); + codegen.generator->write_and_right_operand(equality_test_addr); - // Assign value to bound variable. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(bind_addr); // Destination. - codegen.opcodes.push_back(p_value_addr); // Source. - // Not need to block jump because bind happens only once. + // AND both type and value equality. + codegen.generator->write_end_and(result_addr); + + // We don't need the expression temporary anymore. + if (expr_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + codegen.generator->pop_temporary(); // Remove type equality temporary. + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + return p_previous_test; } break; case GDScriptParser::PatternNode::PT_ARRAY: { - int slevel = p_stack_level; - + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } // Get array type into constant map. - int array_type_addr = codegen.get_constant_pos(Variant::ARRAY); + GDScriptCodeGenerator::Address array_type_addr = codegen.add_constant((int)Variant::ARRAY); + + // Equality is always a boolean. + GDScriptDataType temp_type; + temp_type.has_type = true; + temp_type.kind = GDScriptDataType::BUILTIN; + temp_type.builtin_type = Variant::BOOL; // Check type equality. - int equality_addr = slevel++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(array_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, array_type_addr); + codegen.generator->write_and_left_operand(result_addr); // Store pattern length in constant map. - int array_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size()); + GDScriptCodeGenerator::Address array_length_addr = codegen.add_constant(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size()); // Get value length. - int value_length_addr = slevel++; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::LEN); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(value_length_addr); // Address to result. + temp_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); + Vector<GDScriptCodeGenerator::Address> len_args; + len_args.push_back(p_value_addr); + codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), len_args); // Test length compatibility. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); - codegen.opcodes.push_back(value_length_addr); - codegen.opcodes.push_back(array_length_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if length is not compatible. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, array_length_addr); + codegen.generator->write_and_right_operand(length_compat_addr); + + // AND type and length check. + codegen.generator->write_end_and(result_addr); + + // Remove length temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + // Create temporaries outside the loop so they can be reused. + GDScriptCodeGenerator::Address element_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address test_addr = p_previous_test; // Evaluate element by element. for (int i = 0; i < p_pattern->array.size(); i++) { @@ -1596,495 +1311,491 @@ Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptPar break; } - int stlevel = p_stack_level; - Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block. + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_addr); + // Add index to constant map. - int index_addr = codegen.get_constant_pos(i); + GDScriptCodeGenerator::Address index_addr = codegen.add_constant(i); // Get the actual element from the user-sent array. - int element_addr = stlevel++; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(p_value_addr); // Source. - codegen.opcodes.push_back(index_addr); // Index. - codegen.opcodes.push_back(element_addr); // Destination. + codegen.generator->write_get(element_addr, index_addr, p_value_addr); // Also get type of element. - int element_type_addr = stlevel++; - element_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(element_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(element_type_addr); // Address to result. + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(element_addr); + codegen.generator->write_call_utility(element_type_addr, "typeof", typeof_args); // Try the pattern inside the element. - Error err = _parse_match_pattern(codegen, p_pattern->array[i], stlevel, element_addr, element_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); - if (err != OK) { - return err; + test_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, p_previous_test, false, true); + if (r_error != OK) { + return GDScriptCodeGenerator::Address(); } - // Patch jumps to block to try the next element. - for (int j = 0; j < element_block_patches.size(); j++) { - codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size(); - } + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); } + // Remove element temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - // Also here for the case of empty arrays. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + return test_addr; } break; case GDScriptParser::PatternNode::PT_DICTIONARY: { - int slevel = p_stack_level; - + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } // Get dictionary type into constant map. - int dict_type_addr = codegen.get_constant_pos(Variant::DICTIONARY); + GDScriptCodeGenerator::Address dict_type_addr = codegen.add_constant((int)Variant::DICTIONARY); + + // Equality is always a boolean. + GDScriptDataType temp_type; + temp_type.has_type = true; + temp_type.kind = GDScriptDataType::BUILTIN; + temp_type.builtin_type = Variant::BOOL; // Check type equality. - int equality_addr = slevel++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(dict_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, dict_type_addr); + codegen.generator->write_and_left_operand(result_addr); // Store pattern length in constant map. - int dict_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); + GDScriptCodeGenerator::Address dict_length_addr = codegen.add_constant(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); // Get user's dictionary length. - int value_length_addr = slevel++; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::LEN); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(value_length_addr); // Address to result. + temp_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); + Vector<GDScriptCodeGenerator::Address> func_args; + func_args.push_back(p_value_addr); + codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), func_args); // Test length compatibility. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); - codegen.opcodes.push_back(value_length_addr); - codegen.opcodes.push_back(dict_length_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if length is not compatible. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type); + codegen.generator->write_binary_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, dict_length_addr); + codegen.generator->write_and_right_operand(length_compat_addr); + + // AND type and length check. + codegen.generator->write_end_and(result_addr); + + // Remove length temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + // Create temporaries outside the loop so they can be reused. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address test_result = codegen.add_temporary(temp_type); + GDScriptCodeGenerator::Address element_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address test_addr = p_previous_test; // Evaluate element by element. for (int i = 0; i < p_pattern->dictionary.size(); i++) { const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i]; if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { // Ignore rest pattern. - continue; + break; } - int stlevel = p_stack_level; - Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block. + + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_addr); // Get the pattern key. - int pattern_key_addr = _parse_expression(codegen, element.key, stlevel); - if (pattern_key_addr < 0) { - return ERR_PARSE_ERROR; - } - if ((pattern_key_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - stlevel++; - codegen.alloc_stack(stlevel); + GDScriptCodeGenerator::Address pattern_key_addr = _parse_expression(codegen, r_error, element.key); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - // Create stack slot for test result. - int test_result = stlevel++; - test_result |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(stlevel); - - // Check if pattern key exists in user's dictionary. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); - codegen.opcodes.push_back(1); // Argument count. - codegen.opcodes.push_back(p_value_addr); // Base (user dictionary). - codegen.opcodes.push_back(codegen.get_name_map_pos("has")); // Function name. - codegen.opcodes.push_back(pattern_key_addr); // Argument (pattern key). - codegen.opcodes.push_back(test_result); // Return address. - - // Jump away if key doesn't exist. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(test_result); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + // Check if pattern key exists in user's dictionary. This will be AND-ed with next result. + func_args.clear(); + func_args.push_back(pattern_key_addr); + codegen.generator->write_call(test_result, p_value_addr, "has", func_args); if (element.value_pattern != nullptr) { + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_result); + // Get actual value from user dictionary. - int value_addr = stlevel++; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(p_value_addr); // Source. - codegen.opcodes.push_back(pattern_key_addr); // Index. - codegen.opcodes.push_back(value_addr); // Destination. + codegen.generator->write_get(element_addr, pattern_key_addr, p_value_addr); // Also get type of value. - int value_type_addr = stlevel++; - value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(value_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(value_type_addr); // Address to result. + func_args.clear(); + func_args.push_back(element_addr); + codegen.generator->write_call_utility(element_type_addr, "typeof", func_args); // Try the pattern inside the value. - Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); - if (err != OK) { - return err; + test_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, test_addr, false, true); + if (r_error != OK) { + return GDScriptCodeGenerator::Address(); } + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); } - // Patch jumps to block to try the next element. - for (int j = 0; j < element_block_patches.size(); j++) { - codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size(); + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); + + // Remove pattern key temporary. + if (pattern_key_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } } - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - // Also here for the case of empty dictionaries. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + // Remove element temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + return test_addr; } break; case GDScriptParser::PatternNode::PT_REST: // Do nothing. + return p_previous_test; break; + case GDScriptParser::PatternNode::PT_BIND: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Get the bind address. + GDScriptCodeGenerator::Address bind = codegen.locals[p_pattern->bind->name]; + + // Assign value to bound variable. + codegen.generator->write_assign(bind, p_value_addr); + } + [[fallthrough]]; // Act like matching anything too. case GDScriptParser::PatternNode::PT_WILDCARD: - // This matches anything so just do the jump. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + // If this is a fall through we don't want to do this again. + if (p_pattern->pattern_type != GDScriptParser::PatternNode::PT_BIND) { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + } + // This matches anything so just do the same as `if(true)`. + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the operator with the `true` constant so it works as always matching. + GDScriptCodeGenerator::Address constant = codegen.add_constant(true); + codegen.generator->write_and_right_operand(constant); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the operator with the `true` constant so it works as always matching. + GDScriptCodeGenerator::Address constant = codegen.add_constant(true); + codegen.generator->write_or_right_operand(constant); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign_true(p_previous_test); + } + return p_previous_test; } - return OK; + ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern."); } -Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) { - codegen.push_stack_identifiers(); - int new_identifiers = 0; - codegen.current_line = p_block->start_line; +void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) { + for (int i = 0; i < p_block->locals.size(); i++) { + if (p_block->locals[i].type == GDScriptParser::SuiteNode::Local::PARAMETER || p_block->locals[i].type == GDScriptParser::SuiteNode::Local::FOR_VARIABLE) { + // Parameters are added directly from function and loop variables are declared explicitly. + continue; + } + codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype())); + } +} + +Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals) { + Error error = OK; + GDScriptCodeGenerator *gen = codegen.generator; + + codegen.start_block(); + + if (p_add_locals) { + _add_locals_in_block(codegen, p_block); + } for (int i = 0; i < p_block->statements.size(); i++) { const GDScriptParser::Node *s = p_block->statements[i]; #ifdef DEBUG_ENABLED // Add a newline before each statement, since the debugger needs those. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(s->start_line); - codegen.current_line = s->start_line; + gen->write_newline(s->start_line); #endif switch (s->type) { case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s); - int slevel = p_stack_level; + gen->start_match(); + codegen.start_block(); - // First, let's save the addres of the value match. - int temp_addr = _parse_expression(codegen, match->test, slevel); - if (temp_addr < 0) { - return ERR_PARSE_ERROR; + // Evaluate the match expression. + GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); + GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, error, match->test); + if (error) { + return error; } - if ((temp_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + + // Assign to local. + // TODO: This can be improved by passing the target to parse_expression(). + gen->write_assign(value, value_expr); + + if (value_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } // Then, let's save the type of the value in the stack too, so we can reuse for later comparisons. - int type_addr = slevel++; - type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(temp_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(type_addr); // Address to result. + GDScriptDataType typeof_type; + typeof_type.has_type = true; + typeof_type.kind = GDScriptDataType::BUILTIN; + typeof_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address type = codegen.add_local("@match_type", typeof_type); - Vector<int> patch_match_end; // Will patch the jump to the end of match. + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(value); + gen->write_call_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(); + } + const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - int bound_variables = 0; - codegen.push_stack_identifiers(); // Create an extra block around for binds. + gen->start_match_branch(); // Need so lower level code can patch 'continue' jumps. + codegen.start_block(); // Create an extra block around for binds. + + // Add locals in block before patterns, so temporaries don't use the stack address for binds. + _add_locals_in_block(codegen, branch->block); #ifdef DEBUG_ENABLED // Add a newline before each branch, since the debugger needs those. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(s->start_line); - codegen.current_line = s->start_line; + gen->write_newline(branch->start_line); #endif - Vector<int> patch_addrs; // Will patch with end of pattern to jump. - Vector<int> block_patch_addrs; // Will patch with start of block to jump. - // For each pattern in branch. + GDScriptCodeGenerator::Address pattern_result = codegen.add_temporary(); for (int k = 0; k < branch->patterns.size(); k++) { - if (k > 0) { - // Patch jumps per pattern to allow for multipattern. If a pattern fails it just tries the next. - for (int l = 0; l < patch_addrs.size(); l++) { - codegen.opcodes.write[patch_addrs[l]] = codegen.opcodes.size(); - } - patch_addrs.clear(); + pattern_result = _parse_match_pattern(codegen, error, branch->patterns[k], value, type, pattern_result, k == 0, false); + if (error != OK) { + return error; } - Error err = _parse_match_pattern(codegen, branch->patterns[k], slevel, temp_addr, type_addr, bound_variables, patch_addrs, block_patch_addrs); - if (err != OK) { - return err; - } - } - // Patch jumps to the block. - for (int k = 0; k < block_patch_addrs.size(); k++) { - codegen.opcodes.write[block_patch_addrs[k]] = codegen.opcodes.size(); } - // Leave space for bound variables. - slevel += bound_variables; - codegen.alloc_stack(slevel); - - // Parse the branch block. - _parse_block(codegen, branch->block, slevel, p_break_addr, p_continue_addr); + // Check if pattern did match. + gen->write_if(pattern_result); - // Jump to end of match. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - patch_match_end.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be patched. + // Remove the result from stack. + gen->pop_temporary(); - // Patch the addresses of last pattern to jump to the end of the branch, into the next one. - for (int k = 0; k < patch_addrs.size(); k++) { - codegen.opcodes.write[patch_addrs[k]] = codegen.opcodes.size(); + // Parse the branch block. + error = _parse_block(codegen, branch->block, false); // Don't add locals again. + if (error) { + return error; } - codegen.pop_stack_identifiers(); // Get out of extra block. + codegen.end_block(); // Get out of extra block. } - // Patch the addresses to jump to the end of the match statement. - for (int j = 0; j < patch_match_end.size(); j++) { - codegen.opcodes.write[patch_match_end[j]] = codegen.opcodes.size(); + + // End all nested `if`s. + for (int j = 0; j < match->branches.size(); j++) { + gen->write_endif(); } - } break; + gen->end_match(); + } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s); - int ret2 = _parse_expression(codegen, if_n->condition, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, if_n->condition); + if (error) { + return error; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(ret2); - int else_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(0); //temporary + gen->write_if(condition); + + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } - Error err = _parse_block(codegen, if_n->true_block, p_stack_level, p_break_addr, p_continue_addr); - if (err) { - return err; + error = _parse_block(codegen, if_n->true_block); + if (error) { + return error; } if (if_n->false_block) { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - int end_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - codegen.opcodes.write[else_addr] = codegen.opcodes.size(); - - Error err2 = _parse_block(codegen, if_n->false_block, p_stack_level, p_break_addr, p_continue_addr); - if (err2) { - return err2; - } + gen->write_else(); - codegen.opcodes.write[end_addr] = codegen.opcodes.size(); - } else { - //end without else - codegen.opcodes.write[else_addr] = codegen.opcodes.size(); + error = _parse_block(codegen, if_n->false_block); + if (error) { + return error; + } } + gen->write_endif(); } break; case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); - int slevel = p_stack_level; - int iter_stack_pos = slevel; - int iterator_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - int counter_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - int container_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.alloc_stack(slevel); - - codegen.push_stack_identifiers(); - codegen.add_stack_identifier(for_n->variable->name, iter_stack_pos); - - int ret2 = _parse_expression(codegen, for_n->list, slevel, false); - if (ret2 < 0) { - return ERR_COMPILATION_FAILED; - } - - //assign container - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(container_pos); - codegen.opcodes.push_back(ret2); - - //begin loop - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE_BEGIN); - codegen.opcodes.push_back(counter_pos); - codegen.opcodes.push_back(container_pos); - codegen.opcodes.push_back(codegen.opcodes.size() + 4); - codegen.opcodes.push_back(iterator_pos); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next - codegen.opcodes.push_back(codegen.opcodes.size() + 8); - //break loop - int break_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next - codegen.opcodes.push_back(0); //skip code for next - //next loop - int continue_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE); - codegen.opcodes.push_back(counter_pos); - codegen.opcodes.push_back(container_pos); - codegen.opcodes.push_back(break_pos); - codegen.opcodes.push_back(iterator_pos); - - Error err = _parse_block(codegen, for_n->loop, slevel, break_pos, continue_pos); - if (err) { - return err; - } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(continue_pos); - codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size(); - - codegen.pop_stack_identifiers(); + codegen.start_block(); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype())); + + gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype())); + + GDScriptCodeGenerator::Address list = _parse_expression(codegen, error, for_n->list); + if (error) { + return error; + } + + gen->write_for_assignment(iterator, list); + + if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + gen->write_for(); + + error = _parse_block(codegen, for_n->loop); + if (error) { + return error; + } + + gen->write_endfor(); + + codegen.end_block(); } break; case GDScriptParser::Node::WHILE: { const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - int break_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(0); - int continue_addr = codegen.opcodes.size(); - - int ret2 = _parse_expression(codegen, while_n->condition, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + + gen->start_while_condition(); + + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, while_n->condition); + if (error) { + return error; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(ret2); - codegen.opcodes.push_back(break_addr); - Error err = _parse_block(codegen, while_n->loop, p_stack_level, break_addr, continue_addr); - if (err) { - return err; + + gen->write_while(condition); + + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(continue_addr); - codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); + error = _parse_block(codegen, while_n->loop); + if (error) { + return error; + } + gen->write_endwhile(); } break; case GDScriptParser::Node::BREAK: { - if (p_break_addr < 0) { - _set_error("'break'' not within loop", s); - return ERR_COMPILATION_FAILED; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(p_break_addr); - + gen->write_break(); } break; case GDScriptParser::Node::CONTINUE: { - if (p_continue_addr < 0) { - _set_error("'continue' not within loop", s); - return ERR_COMPILATION_FAILED; + const GDScriptParser::ContinueNode *cont = static_cast<const GDScriptParser::ContinueNode *>(s); + if (cont->is_for_match) { + gen->write_continue_match(); + } else { + gen->write_continue(); } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(p_continue_addr); - } break; case GDScriptParser::Node::RETURN: { const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s); - int ret2; + + GDScriptCodeGenerator::Address return_value; if (return_n->return_value != nullptr) { - ret2 = _parse_expression(codegen, return_n->return_value, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + return_value = _parse_expression(codegen, error, return_n->return_value); + if (error) { + return error; } - - } else { - ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN); - codegen.opcodes.push_back(ret2); - + gen->write_return(return_value); + if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } } break; case GDScriptParser::Node::ASSERT: { #ifdef DEBUG_ENABLED - // try subblocks - const GDScriptParser::AssertNode *as = static_cast<const GDScriptParser::AssertNode *>(s); - int ret2 = _parse_expression(codegen, as->condition, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, as->condition); + if (error) { + return error; } - int message_ret = 0; + GDScriptCodeGenerator::Address message; + if (as->message) { - message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false); - if (message_ret < 0) { - return ERR_PARSE_ERROR; + message = _parse_expression(codegen, error, as->message); + if (error) { + return error; } } + gen->write_assert(condition, message); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT); - codegen.opcodes.push_back(ret2); - codegen.opcodes.push_back(message_ret); + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + if (message.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } #endif } break; case GDScriptParser::Node::BREAKPOINT: { #ifdef DEBUG_ENABLED - // try subblocks - codegen.opcodes.push_back(GDScriptFunction::OPCODE_BREAKPOINT); + gen->write_breakpoint(); #endif } break; case GDScriptParser::Node::VARIABLE: { const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); - - // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. - // - //if (_is_class_member_property(codegen, lv->name)) { - // _set_error("Name for local variable '" + String(lv->name) + "' can't shadow class property of the same name.", lv); - // return ERR_ALREADY_EXISTS; - //} - - codegen.add_stack_identifier(lv->identifier->name, p_stack_level++); - codegen.alloc_stack(p_stack_level); - new_identifiers++; + // Should be already in stack when the block began. + GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; + GDScriptParser::DataType local_type = lv->get_datatype(); if (lv->initializer != nullptr) { - int dst_address = codegen.stack_identifiers[lv->identifier->name]; - dst_address |= GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS; - - int src_address = _parse_expression(codegen, lv->initializer, p_stack_level); - if (src_address < 0) { - return ERR_PARSE_ERROR; + // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. + if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) { + if (local_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else { + codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); + } } - if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) { - return ERR_PARSE_ERROR; + 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(); + } + } else if (lv->get_datatype().is_hard_type()) { + // Initialize with default for type. + if (local_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + // The `else` branch is for objects, in such case we leave it as `null`. } } break; case GDScriptParser::Node::CONSTANT: { @@ -2094,281 +1805,183 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _set_error("Local constant must have a constant value as initializer.", lc->initializer); return ERR_PARSE_ERROR; } - codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(lc->initializer->reduced_value); + + codegen.add_local_constant(lc->identifier->name, lc->initializer->reduced_value); } break; case GDScriptParser::Node::PASS: // Nothing to do. break; default: { - //expression + // Expression. if (s->is_expression()) { - int ret2 = _parse_expression(codegen, static_cast<const GDScriptParser::ExpressionNode *>(s), p_stack_level, true); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address expr = _parse_expression(codegen, error, static_cast<const GDScriptParser::ExpressionNode *>(s), true); + if (error) { + return error; + } + if (expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } } else { - ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); //unreachable code + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); // Unreachable code. } } break; } } - codegen.pop_stack_identifiers(); + codegen.end_block(); return OK; } -Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { - Vector<int> bytecode; +GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready, bool p_for_lambda) { + r_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) { + if (p_func->identifier) { + func_name = p_func->identifier->name; + } else { + func_name = "<anonymous lambda>"; + } + is_static = p_func->is_static; + rpc_mode = p_func->rpc_mode; + return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); + } else { + if (p_for_ready) { + func_name = "_ready"; + } else { + func_name = "@implicit_new"; + } + } + + codegen.function_name = func_name; + codegen.generator->write_start(p_script, func_name, is_static, rpc_mode, return_type); + int optional_parameters = 0; if (p_func) { for (int i = 0; i < p_func->parameters.size(); i++) { - // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. - // - //if (_is_class_member_property(p_script, p_func->arguments[i])) { - // _set_error("Name for argument '" + String(p_func->arguments[i]) + "' can't shadow class property of the same name.", p_func); - // return ERR_ALREADY_EXISTS; - //} - - codegen.add_stack_identifier(p_func->parameters[i]->identifier->name, i); -#ifdef TOOLS_ENABLED - argnames.push_back(p_func->parameters[i]->identifier->name); -#endif + const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; + GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script); + uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type); + codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type); + if (p_func->parameters[i]->default_value != nullptr) { optional_parameters++; } } - stack_level = p_func->parameters.size(); } - codegen.alloc_stack(stack_level); - - /* Parse initializer -if applies- */ - - bool is_implicit_initializer = !p_for_ready && !p_func; - bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; + // Parse initializer if applies. + bool is_implicit_initializer = !p_for_ready && !p_func && !p_for_lambda; + bool is_initializer = p_func && !p_for_lambda && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; + bool is_for_ready = p_for_ready || (p_func && !p_for_lambda && String(p_func->identifier->name) == "_ready"); - if (is_implicit_initializer) { + if (!p_for_lambda && (is_implicit_initializer || is_for_ready)) { // Initialize class fields. for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { continue; } const GDScriptParser::VariableNode *field = p_class->members[i].variable; - if (field->onready) { + if (field->onready != is_for_ready) { // Only initialize in _ready. continue; } + GDScriptParser::DataType field_type = field->get_datatype(); + + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); if (field->initializer) { // Emit proper line change. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(field->initializer->start_line); + codegen.generator->write_newline(field->initializer->start_line); - int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true); - if (src_address < 0) { - return ERR_PARSE_ERROR; + // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. + if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type()) { + if (field_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else { + codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); + } } - int dst_address = codegen.script->member_indices[field->identifier->name].index; - dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; - - if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true); + if (r_error) { + memdelete(codegen.generator); + return nullptr; } - } - } - } - if (p_for_ready || (p_func && String(p_func->identifier->name) == "_ready")) { - // Initialize class fields on ready. - for (int i = 0; i < p_class->members.size(); i++) { - if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { - continue; - } - const GDScriptParser::VariableNode *field = p_class->members[i].variable; - if (!field->onready) { - continue; - } - - if (field->initializer) { - // Emit proper line change. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(field->initializer->start_line); - - int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true); - if (src_address < 0) { - return ERR_PARSE_ERROR; + codegen.generator->write_assign(dst_address, src_address); + if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } - int dst_address = codegen.script->member_indices[field->identifier->name].index; - dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; + } else if (field->get_datatype().is_hard_type()) { + codegen.generator->write_newline(field->start_line); - if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { - return ERR_PARSE_ERROR; + // Initialize with default for type. + if (field_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } + // The `else` branch is for objects, in such case we leave it as `null`. } } } - /* Parse default argument code -if applies- */ - - Vector<int> defarg_addr; - StringName func_name; - + // Parse default argument code if applies. if (p_func) { if (optional_parameters > 0) { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); - defarg_addr.push_back(codegen.opcodes.size()); + codegen.generator->start_parameters(); for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { - int src_addr = _parse_expression(codegen, p_func->parameters[i]->default_value, stack_level, true); - if (src_addr < 0) { - return ERR_PARSE_ERROR; + const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; + GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value, true); + if (r_error) { + memdelete(codegen.generator); + return nullptr; } - int dst_addr = codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); - if (!_generate_typed_assign(codegen, src_addr, dst_addr, _gdtype_from_datatype(p_func->parameters[i]->get_datatype()), p_func->parameters[i]->default_value->get_datatype())) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; + codegen.generator->write_assign_default_parameter(dst_addr, src_addr); + if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } - defarg_addr.push_back(codegen.opcodes.size()); } - defarg_addr.invert(); + codegen.generator->end_parameters(); } - func_name = p_func->identifier->name; - codegen.function_name = func_name; - Error err = _parse_block(codegen, p_func->body, stack_level); - if (err) { - return err; + r_error = _parse_block(codegen, p_func->body); + if (r_error) { + memdelete(codegen.generator); + return nullptr; } - - } else { - if (p_for_ready) { - func_name = "_ready"; - } else { - func_name = "@implicit_new"; - } - } - - codegen.function_name = func_name; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); - - /* - if (String(p_func->name)=="") { //initializer func - gdfunc = &p_script->initializer; - */ - //} else { //regular func - p_script->member_functions[func_name] = memnew(GDScriptFunction); - GDScriptFunction *gdfunc = p_script->member_functions[func_name]; - //} - - if (p_func) { - gdfunc->_static = p_func->is_static; - gdfunc->rpc_mode = p_func->rpc_mode; - gdfunc->argument_types.resize(p_func->parameters.size()); - for (int i = 0; i < p_func->parameters.size(); i++) { - gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->parameters[i]->get_datatype()); - } - gdfunc->return_type = _gdtype_from_datatype(p_func->get_datatype()); - } else { - gdfunc->_static = false; - gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; - gdfunc->return_type = GDScriptDataType(); - gdfunc->return_type.has_type = true; - gdfunc->return_type.kind = GDScriptDataType::BUILTIN; - gdfunc->return_type.builtin_type = Variant::NIL; - } - -#ifdef TOOLS_ENABLED - gdfunc->arg_names = argnames; -#endif - //constants - if (codegen.constant_map.size()) { - gdfunc->_constant_count = codegen.constant_map.size(); - gdfunc->constants.resize(codegen.constant_map.size()); - gdfunc->_constants_ptr = gdfunc->constants.ptrw(); - const Variant *K = nullptr; - while ((K = codegen.constant_map.next(K))) { - int idx = codegen.constant_map[*K]; - gdfunc->constants.write[idx] = *K; - } - } else { - gdfunc->_constants_ptr = nullptr; - gdfunc->_constant_count = 0; - } - //global names - if (codegen.name_map.size()) { - gdfunc->global_names.resize(codegen.name_map.size()); - gdfunc->_global_names_ptr = &gdfunc->global_names[0]; - for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) { - gdfunc->global_names.write[E->get()] = E->key(); - } - gdfunc->_global_names_count = gdfunc->global_names.size(); - - } else { - gdfunc->_global_names_ptr = nullptr; - gdfunc->_global_names_count = 0; - } - -#ifdef TOOLS_ENABLED - // Named globals - if (codegen.named_globals.size()) { - gdfunc->named_globals.resize(codegen.named_globals.size()); - gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); - for (int i = 0; i < codegen.named_globals.size(); i++) { - gdfunc->named_globals.write[i] = codegen.named_globals[i]; - } - gdfunc->_named_globals_count = gdfunc->named_globals.size(); } -#endif - - if (codegen.opcodes.size()) { - gdfunc->code = codegen.opcodes; - gdfunc->_code_ptr = &gdfunc->code[0]; - gdfunc->_code_size = codegen.opcodes.size(); - } else { - gdfunc->_code_ptr = nullptr; - gdfunc->_code_size = 0; - } - - if (defarg_addr.size()) { - gdfunc->default_arguments = defarg_addr; - gdfunc->_default_arg_count = defarg_addr.size() - 1; - gdfunc->_default_arg_ptr = &gdfunc->default_arguments[0]; - } else { - gdfunc->_default_arg_count = 0; - gdfunc->_default_arg_ptr = nullptr; - } - - gdfunc->_argument_count = p_func ? p_func->parameters.size() : 0; - gdfunc->_stack_size = codegen.stack_max; - gdfunc->_call_size = codegen.call_max; - gdfunc->name = func_name; #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { String signature; - //path + // Path. if (p_script->get_path() != String()) { signature += p_script->get_path(); } - //loc + // Location. if (p_func) { signature += "::" + itos(p_func->body->start_line); } else { signature += "::0"; } - //function and class + // Function and class. if (p_class->identifier) { signature += "::" + String(p_class->identifier->name) + "." + String(func_name); @@ -2376,65 +1989,65 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser signature += "::" + String(func_name); } - gdfunc->profile.signature = signature; - } -#endif - gdfunc->_script = p_script; - gdfunc->source = source; - -#ifdef DEBUG_ENABLED + if (p_for_lambda) { + signature += "(lambda)"; + } - { - gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); - gdfunc->_func_cname = gdfunc->func_cname.get_data(); + codegen.generator->set_signature(signature); } - #endif + if (p_func) { - gdfunc->_initial_line = p_func->start_line; + codegen.generator->set_initial_line(p_func->start_line); #ifdef TOOLS_ENABLED - - p_script->member_lines[func_name] = p_func->start_line; + if (!p_for_lambda) { + p_script->member_lines[func_name] = p_func->start_line; + p_script->doc_functions[func_name] = p_func->doc_description; + } #endif } else { - gdfunc->_initial_line = 0; + codegen.generator->set_initial_line(0); } - if (codegen.debug_stack) { - gdfunc->stack_debug = codegen.stack_debug; - } + GDScriptFunction *gd_function = codegen.generator->write_end(); if (is_initializer) { - p_script->initializer = gdfunc; + p_script->initializer = gd_function; + } else if (is_implicit_initializer) { + p_script->implicit_initializer = gd_function; } - if (is_implicit_initializer) { - p_script->implicit_initializer = gdfunc; + + if (p_func) { + // if no return statement -> return type is void not unresolved Variant + if (p_func->body->has_return) { + gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype()); + } else { + gd_function->return_type = GDScriptDataType(); + gd_function->return_type.has_type = true; + gd_function->return_type.kind = GDScriptDataType::BUILTIN; + gd_function->return_type.builtin_type = Variant::NIL; + } +#ifdef TOOLS_ENABLED + gd_function->default_arg_values = p_func->default_arg_values; +#endif } - return OK; + if (!p_for_lambda) { + p_script->member_functions[func_name] = gd_function; + } + + memdelete(codegen.generator); + + return gd_function; } Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { - Vector<int> bytecode; + Error error = OK; CodeGen codegen; + codegen.generator = memnew(GDScriptByteCodeGenerator); codegen.class_node = p_class; codegen.script = p_script; - codegen.function_node = nullptr; - codegen.stack_max = 0; - codegen.current_line = 0; - codegen.call_max = 0; - codegen.debug_stack = EngineDebugger::is_active(); - Vector<StringName> argnames; - - int stack_level = 0; - - if (p_is_setter) { - codegen.add_stack_identifier(p_variable->setter_parameter->name, stack_level++); - argnames.push_back(p_variable->setter_parameter->name); - } - - codegen.alloc_stack(stack_level); StringName func_name; @@ -2443,76 +2056,35 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP } else { func_name = "@" + p_variable->identifier->name + "_getter"; } + codegen.function_name = func_name; - Error err = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter, stack_level); - if (err != OK) { - return err; + GDScriptDataType return_type; + if (p_is_setter) { + return_type.has_type = true; + return_type.kind = GDScriptDataType::BUILTIN; + return_type.builtin_type = Variant::NIL; + } else { + return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); - - p_script->member_functions[func_name] = memnew(GDScriptFunction); - GDScriptFunction *gdfunc = p_script->member_functions[func_name]; + codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type); - gdfunc->_static = false; - gdfunc->rpc_mode = p_variable->rpc_mode; - gdfunc->argument_types.resize(p_is_setter ? 1 : 0); - gdfunc->return_type = _gdtype_from_datatype(p_variable->get_datatype()); -#ifdef TOOLS_ENABLED - gdfunc->arg_names = argnames; -#endif - - // TODO: Unify this with function compiler. - //constants - if (codegen.constant_map.size()) { - gdfunc->_constant_count = codegen.constant_map.size(); - gdfunc->constants.resize(codegen.constant_map.size()); - gdfunc->_constants_ptr = gdfunc->constants.ptrw(); - const Variant *K = nullptr; - while ((K = codegen.constant_map.next(K))) { - int idx = codegen.constant_map[*K]; - gdfunc->constants.write[idx] = *K; - } - } else { - gdfunc->_constants_ptr = nullptr; - gdfunc->_constant_count = 0; + if (p_is_setter) { + uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); + codegen.parameters[p_variable->setter_parameter->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, _gdtype_from_datatype(p_variable->get_datatype())); } - //global names - if (codegen.name_map.size()) { - gdfunc->global_names.resize(codegen.name_map.size()); - gdfunc->_global_names_ptr = &gdfunc->global_names[0]; - for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) { - gdfunc->global_names.write[E->get()] = E->key(); - } - gdfunc->_global_names_count = gdfunc->global_names.size(); - } else { - gdfunc->_global_names_ptr = nullptr; - gdfunc->_global_names_count = 0; + error = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter); + if (error) { + memdelete(codegen.generator); + return error; } -#ifdef TOOLS_ENABLED - // Named globals - if (codegen.named_globals.size()) { - gdfunc->named_globals.resize(codegen.named_globals.size()); - gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); - for (int i = 0; i < codegen.named_globals.size(); i++) { - gdfunc->named_globals.write[i] = codegen.named_globals[i]; - } - gdfunc->_named_globals_count = gdfunc->named_globals.size(); - } -#endif + GDScriptFunction *gd_function = codegen.generator->write_end(); + + p_script->member_functions[func_name] = gd_function; - gdfunc->code = codegen.opcodes; - gdfunc->_code_ptr = &gdfunc->code[0]; - gdfunc->_code_size = codegen.opcodes.size(); - gdfunc->_default_arg_count = 0; - gdfunc->_default_arg_ptr = nullptr; - gdfunc->_argument_count = argnames.size(); - gdfunc->_stack_size = codegen.stack_max; - gdfunc->_call_size = codegen.call_max; - gdfunc->name = func_name; #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { String signature; @@ -2531,29 +2103,15 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP signature += "::" + String(func_name); } - gdfunc->profile.signature = signature; + codegen.generator->set_signature(signature); } #endif - gdfunc->_script = p_script; - gdfunc->source = source; - -#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 - gdfunc->_initial_line = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; #ifdef TOOLS_ENABLED - - p_script->member_lines[func_name] = gdfunc->_initial_line; + p_script->member_lines[func_name] = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; #endif - - if (codegen.debug_stack) { - gdfunc->stack_debug = codegen.stack_debug; - } + memdelete(codegen.generator); return OK; } @@ -2575,6 +2133,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } +#ifdef TOOLS_ENABLED + p_script->doc_functions.clear(); + p_script->doc_variables.clear(); + p_script->doc_constants.clear(); + p_script->doc_enums.clear(); + p_script->doc_signals.clear(); + p_script->doc_tutorials.clear(); + + p_script->doc_brief_description = p_class->doc_brief_description; + p_script->doc_description = p_class->doc_description; + for (int i = 0; i < p_class->doc_tutorials.size(); i++) { + DocData::TutorialDoc td; + td.title = p_class->doc_tutorials[i].first; + td.link = p_class->doc_tutorials[i].second; + p_script->doc_tutorials.append(td); + } +#endif + p_script->native = Ref<GDScriptNativeClass>(); p_script->base = Ref<GDScript>(); p_script->_base = nullptr; @@ -2604,25 +2180,38 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->native = native; } break; case GDScriptDataType::GDSCRIPT: { - Ref<GDScript> base = base_type.script_type; + Ref<GDScript> base = Ref<GDScript>(base_type.script_type); p_script->base = base; p_script->_base = base.ptr(); if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { - if (!parsed_classes.has(p_script->_base)) { - if (parsing_classes.has(p_script->_base)) { - String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; - _set_error("Cyclic class reference for '" + class_name + "'.", p_class); - return ERR_PARSE_ERROR; + if (p_class->base_type.script_path == main_script->path) { + if (!parsed_classes.has(p_script->_base)) { + if (parsing_classes.has(p_script->_base)) { + String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; + _set_error("Cyclic class reference for '" + class_name + "'.", p_class); + return ERR_PARSE_ERROR; + } + Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); + if (err) { + return err; + } } - Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); + } else { + Error err = OK; + base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path); if (err) { return err; } + if (base.is_null() && !base->is_valid()) { + return ERR_COMPILATION_FAILED; + } } } p_script->member_indices = base->member_indices; + native = base->native; + p_script->native = native; } break; default: { _set_error("Parser bug: invalid inheritance.", p_class); @@ -2660,7 +2249,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar break; } minfo.rpc_mode = variable->rpc_mode; - minfo.data_type = _gdtype_from_datatype(variable->get_datatype()); + minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script); PropertyInfo prop_info = minfo.data_type; prop_info.name = name; @@ -2674,20 +2263,22 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; prop_info.usage = export_info.usage; + } + prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; #ifdef TOOLS_ENABLED - if (variable->initializer != nullptr && variable->initializer->type == GDScriptParser::Node::LITERAL) { - p_script->member_default_values[name] = static_cast<const GDScriptParser::LiteralNode *>(variable->initializer)->value; - } + 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); #ifdef TOOLS_ENABLED + if (variable->initializer != nullptr && variable->initializer->is_constant) { + p_script->member_default_values[name] = variable->initializer->reduced_value; + } else { + p_script->member_default_values.erase(name); + } p_script->member_lines[name] = variable->start_line; #endif } break; @@ -2696,14 +2287,12 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar const GDScriptParser::ConstantNode *constant = member.constant; StringName name = constant->identifier->name; - ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL); - - const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(constant->initializer); - - p_script->constants.insert(name, literal->value); + p_script->constants.insert(name, constant->initializer->reduced_value); #ifdef TOOLS_ENABLED - p_script->member_lines[name] = constant->start_line; + if (constant->doc_description != String()) { + p_script->doc_constants[name] = constant->doc_description; + } #endif } break; @@ -2714,6 +2303,15 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->constants.insert(name, enum_value.value); #ifdef TOOLS_ENABLED p_script->member_lines[name] = enum_value.identifier->start_line; + if (!p_script->doc_enums.has("@unnamed_enums")) { + p_script->doc_enums["@unnamed_enums"] = DocData::EnumDoc(); + p_script->doc_enums["@unnamed_enums"].name = "@unnamed_enums"; + } + DocData::ConstantDoc const_doc; + const_doc.name = enum_value.identifier->name; + const_doc.value = Variant(enum_value.value).operator String(); // TODO-DOC: enum value currently is int. + const_doc.description = enum_value.doc_description; + p_script->doc_enums["@unnamed_enums"].values.push_back(const_doc); #endif } break; @@ -2749,6 +2347,11 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar parameters_names.write[j] = signal->parameters[j]->identifier->name; } p_script->_signals[name] = parameters_names; +#ifdef TOOLS_ENABLED + if (!signal->doc_description.is_empty()) { + p_script->doc_signals[name] = signal->doc_description; + } +#endif } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -2765,6 +2368,16 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->constants.insert(enum_n->identifier->name, new_enum); #ifdef TOOLS_ENABLED p_script->member_lines[enum_n->identifier->name] = enum_n->start_line; + p_script->doc_enums[enum_n->identifier->name] = DocData::EnumDoc(); + p_script->doc_enums[enum_n->identifier->name].name = enum_n->identifier->name; + p_script->doc_enums[enum_n->identifier->name].description = enum_n->doc_description; + for (int j = 0; j < enum_n->values.size(); j++) { + DocData::ConstantDoc const_doc; + const_doc.name = enum_n->values[j].identifier->name; + const_doc.value = Variant(enum_n->values[j].value).operator String(); + const_doc.description = enum_n->values[j].doc_description; + p_script->doc_enums[enum_n->identifier->name].values.push_back(const_doc); + } #endif } break; default: @@ -2818,7 +2431,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa if (!has_ready && function->identifier->name == "_ready") { has_ready = true; } - Error err = _parse_function(p_script, p_class, function); + Error err = OK; + _parse_function(err, p_script, p_class, function); if (err) { return err; } @@ -2843,7 +2457,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa { // Create an implicit constructor in any case. - Error err = _parse_function(p_script, p_class, nullptr); + Error err = OK; + _parse_function(err, p_script, p_class, nullptr); if (err) { return err; } @@ -2851,7 +2466,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa if (!has_ready && p_class->onready_used) { //create a _ready constructor - Error err = _parse_function(p_script, p_class, nullptr, true); + Error err = OK; + _parse_function(err, p_script, p_class, nullptr, true); if (err) { return err; } @@ -2892,7 +2508,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa p_script->initializer->call(instance, nullptr, 0, ce); if (ce.error != Callable::CallError::CALL_OK) { - //well, tough luck, not goinna do anything here + //well, tough luck, not gonna do anything here } } #endif diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index e8601f69c7..7d5bee93ac 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,111 +31,86 @@ #ifndef GDSCRIPT_COMPILER_H #define GDSCRIPT_COMPILER_H -#include "core/set.h" +#include "core/templates/set.h" #include "gdscript.h" +#include "gdscript_codegen.h" #include "gdscript_function.h" #include "gdscript_parser.h" class GDScriptCompiler { - const GDScriptParser *parser; + const GDScriptParser *parser = nullptr; Set<GDScript *> parsed_classes; Set<GDScript *> parsing_classes; - GDScript *main_script; + GDScript *main_script = nullptr; + struct CodeGen { - GDScript *script; - const GDScriptParser::ClassNode *class_node; - const GDScriptParser::FunctionNode *function_node; + GDScript *script = nullptr; + const GDScriptParser::ClassNode *class_node = nullptr; + const GDScriptParser::FunctionNode *function_node = nullptr; StringName function_name; - bool debug_stack; - - List<Map<StringName, int>> stack_id_stack; - Map<StringName, int> stack_identifiers; - - List<GDScriptFunction::StackDebug> stack_debug; - List<Map<StringName, int>> block_identifier_stack; - Map<StringName, int> block_identifiers; - Map<StringName, int> local_named_constants; - - void add_stack_identifier(const StringName &p_id, int p_stackpos) { - stack_identifiers[p_id] = p_stackpos; - if (debug_stack) { - block_identifiers[p_id] = p_stackpos; - GDScriptFunction::StackDebug sd; - sd.added = true; - sd.line = current_line; - sd.identifier = p_id; - sd.pos = p_stackpos; - stack_debug.push_back(sd); - } + GDScriptCodeGenerator *generator = nullptr; + Map<StringName, GDScriptCodeGenerator::Address> parameters; + Map<StringName, GDScriptCodeGenerator::Address> locals; + List<Map<StringName, GDScriptCodeGenerator::Address>> locals_stack; + + GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) { + uint32_t addr = generator->add_local(p_name, p_type); + locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_VARIABLE, addr, p_type); + return locals[p_name]; } - void push_stack_identifiers() { - stack_id_stack.push_back(stack_identifiers); - if (debug_stack) { - block_identifier_stack.push_back(block_identifiers); - block_identifiers.clear(); - } + GDScriptCodeGenerator::Address add_local_constant(const StringName &p_name, const Variant &p_value) { + uint32_t addr = generator->add_local_constant(p_name, p_value); + locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CONSTANT, addr); + return locals[p_name]; } - void pop_stack_identifiers() { - stack_identifiers = stack_id_stack.back()->get(); - stack_id_stack.pop_back(); - - if (debug_stack) { - for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { - GDScriptFunction::StackDebug sd; - sd.added = false; - sd.identifier = E->key(); - sd.line = current_line; - sd.pos = E->get(); - stack_debug.push_back(sd); - } - block_identifiers = block_identifier_stack.back()->get(); - block_identifier_stack.pop_back(); - } + GDScriptCodeGenerator::Address add_temporary(const GDScriptDataType &p_type = GDScriptDataType()) { + uint32_t addr = generator->add_temporary(p_type); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::TEMPORARY, addr, p_type); } - HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; - Map<StringName, int> name_map; -#ifdef TOOLS_ENABLED - Vector<StringName> named_globals; -#endif - - int get_name_map_pos(const StringName &p_identifier) { - int ret; - if (!name_map.has(p_identifier)) { - ret = name_map.size(); - name_map[p_identifier] = ret; - } else { - ret = name_map[p_identifier]; + GDScriptCodeGenerator::Address add_constant(const Variant &p_constant) { + GDScriptDataType type; + type.has_type = true; + type.kind = GDScriptDataType::BUILTIN; + type.builtin_type = p_constant.get_type(); + if (type.builtin_type == Variant::OBJECT) { + Object *obj = p_constant; + if (obj) { + type.kind = GDScriptDataType::NATIVE; + type.native_type = obj->get_class_name(); + + Ref<Script> script = obj->get_script(); + if (script.is_valid()) { + type.script_type = script.ptr(); + Ref<GDScript> gdscript = script; + if (gdscript.is_valid()) { + type.kind = GDScriptDataType::GDSCRIPT; + } else { + type.kind = GDScriptDataType::SCRIPT; + } + } + } else { + type.builtin_type = Variant::NIL; + } } - return ret; - } - int get_constant_pos(const Variant &p_constant) { - if (constant_map.has(p_constant)) { - return constant_map[p_constant] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); - } - int pos = constant_map.size(); - constant_map[p_constant] = pos; - return pos | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + uint32_t addr = generator->add_or_get_constant(p_constant); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CONSTANT, addr, type); } - Vector<int> opcodes; - void alloc_stack(int p_level) { - if (p_level >= stack_max) { - stack_max = p_level + 1; - } - } - void alloc_call(int p_params) { - if (p_params >= call_max) { - call_max = p_params; - } + void start_block() { + Map<StringName, GDScriptCodeGenerator::Address> old_locals = locals; + locals_stack.push_back(old_locals); + generator->start_block(); } - int current_line; - int stack_max; - int call_max; + void end_block() { + locals = locals_stack.back()->get(); + locals_stack.pop_back(); + generator->end_block(); + } }; bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); @@ -143,24 +118,23 @@ class GDScriptCompiler { void _set_error(const String &p_error, const GDScriptParser::Node *p_node); - bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level); - bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); - bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); - bool _generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type); + Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const; - int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr = 0); - int _parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0); - Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address); - Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1); - Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); + 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); + GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = 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); - int err_line; - int err_column; + int err_line = 0; + int err_column = 0; StringName source; String error; bool within_await = false; diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp new file mode 100644 index 0000000000..789af57b4c --- /dev/null +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -0,0 +1,989 @@ +/*************************************************************************/ +/* 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_MEMBER: { + return "member(" + p_script->debug_get_member_by_index(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_CONSTANT: { + return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_STACK: { + switch (addr) { + case GDScriptFunction::ADDR_STACK_SELF: + return "self"; + case GDScriptFunction::ADDR_STACK_CLASS: + return "class"; + case GDScriptFunction::ADDR_STACK_NIL: + return "nil"; + default: + return "stack(" + itos(addr) + ")"; + } + } 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_ARRAY: { + text += "assign typed array "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 3; + } 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_TYPED_ARRAY: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + + Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4]; + StringName native_type = get_global_name(_code_ptr[ip + argc + 5]); + + String type_name; + if (script_type.is_valid() && script_type->is_valid()) { + type_name = script_type->get_path(); + } else if (native_type != StringName()) { + type_name = native_type; + } else { + type_name = Variant::get_type_name(builtin_type); + } + + text += " make_typed_array ("; + text += type_name; + text += ") "; + + 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_CREATE_LAMBDA: { + int captures_count = _code_ptr[ip + 1 + instr_var_args]; + GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]]; + + text += DADDR(1 + captures_count); + text += "create lambda from "; + text += lambda->name.operator String(); + text += "function, captures ("; + + for (int i = 0; i < captures_count; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i); + } + text += ")"; + + incr = 3 + captures_count; + } 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; + case OPCODE_RETURN_TYPED_BUILTIN: { + text += "return typed builtin ("; + text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 2]); + text += ") "; + text += DADDR(1); + + incr += 3; + } break; + case OPCODE_RETURN_TYPED_ARRAY: { + text += "return typed array "; + text += DADDR(1); + + incr += 5; + } break; + case OPCODE_RETURN_TYPED_NATIVE: { + text += "return typed native ("; + text += DADDR(2); + text += ") "; + text += DADDR(1); + + incr += 3; + } break; + case OPCODE_RETURN_TYPED_SCRIPT: { + Variant script = _constants_ptr[_code_ptr[ip + 2]]; + Script *sc = Object::cast_to<Script>(script.operator Object *()); + + text += "return typed script ("; + text += sc->get_path(); + text += ") "; + text += DADDR(1); + + incr += 3; + } 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_STORE_NAMED_GLOBAL: { + text += "store named global "; + text += DADDR(1); + text += " = "; + text += String(_global_names_ptr[_code_ptr[ip + 2]]); + + incr += 3; + } break; + 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; + +#define DISASSEMBLE_TYPE_ADJUST(m_v_type) \ + case OPCODE_TYPE_ADJUST_##m_v_type: { \ + text += "type adjust ("; \ + text += #m_v_type; \ + text += ") "; \ + text += DADDR(1); \ + incr += 2; \ + } break + + DISASSEMBLE_TYPE_ADJUST(BOOL); + DISASSEMBLE_TYPE_ADJUST(INT); + DISASSEMBLE_TYPE_ADJUST(FLOAT); + DISASSEMBLE_TYPE_ADJUST(STRING); + DISASSEMBLE_TYPE_ADJUST(VECTOR2); + DISASSEMBLE_TYPE_ADJUST(VECTOR2I); + DISASSEMBLE_TYPE_ADJUST(RECT2); + DISASSEMBLE_TYPE_ADJUST(RECT2I); + DISASSEMBLE_TYPE_ADJUST(VECTOR3); + DISASSEMBLE_TYPE_ADJUST(VECTOR3I); + DISASSEMBLE_TYPE_ADJUST(TRANSFORM2D); + DISASSEMBLE_TYPE_ADJUST(PLANE); + DISASSEMBLE_TYPE_ADJUST(QUAT); + DISASSEMBLE_TYPE_ADJUST(AABB); + DISASSEMBLE_TYPE_ADJUST(BASIS); + DISASSEMBLE_TYPE_ADJUST(TRANSFORM); + DISASSEMBLE_TYPE_ADJUST(COLOR); + DISASSEMBLE_TYPE_ADJUST(STRING_NAME); + DISASSEMBLE_TYPE_ADJUST(NODE_PATH); + DISASSEMBLE_TYPE_ADJUST(RID); + DISASSEMBLE_TYPE_ADJUST(OBJECT); + DISASSEMBLE_TYPE_ADJUST(CALLABLE); + DISASSEMBLE_TYPE_ADJUST(SIGNAL); + DISASSEMBLE_TYPE_ADJUST(DICTIONARY); + DISASSEMBLE_TYPE_ADJUST(ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_BYTE_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_INT32_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_INT64_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT32_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT64_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_STRING_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR2_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR3_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_COLOR_ARRAY); + + 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 88bf8cde0f..6ae825d2bd 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,16 +30,17 @@ #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/project_settings.h" +#include "core/config/project_settings.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" #endif @@ -122,10 +123,10 @@ static void get_function_names_recursively(const GDScriptParser::ClassNode *p_cl for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::FUNCTION) { const GDScriptParser::FunctionNode *function = p_class->members[i].function; - r_funcs[function->start_line] = p_prefix.empty() ? String(function->identifier->name) : p_prefix + "." + String(function->identifier->name); + 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.empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs); + get_function_names_recursively(p_class->members[i].m_class, p_prefix.is_empty() ? new_prefix : p_prefix + "." + new_prefix, r_funcs); } } } @@ -193,6 +194,10 @@ bool GDScriptLanguage::supports_builtin_mode() const { return true; } +bool GDScriptLanguage::supports_documentation() const { + return true; +} + int GDScriptLanguage::find_function(const String &p_function, const String &p_code) const { GDScriptTokenizer tokenizer; tokenizer.set_source_code(p_code); @@ -383,8 +388,8 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> } bool skip = false; - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (E->key() == GlobalConstants::get_global_constant_name(i)) { + for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { + if (E->key() == CoreConstants::get_global_constant_name(i)) { skip = true; break; } @@ -407,11 +412,14 @@ void GDScriptLanguage::get_recognized_extensions(List<String> *p_extensions) con } void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const { - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - p_functions->push_back(GDScriptFunctions::get_info(GDScriptFunctions::Function(i))); + List<StringName> functions; + GDScriptUtilityFunctions::get_function_list(&functions); + + 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"; @@ -468,7 +476,7 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na 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; } } @@ -483,6 +491,8 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na #ifdef TOOLS_ENABLED +#define COMPLETION_RECURSION_LIMIT 200 + struct GDScriptCompletionIdentifier { GDScriptParser::DataType type; String enumeration; @@ -490,36 +500,6 @@ struct GDScriptCompletionIdentifier { const GDScriptParser::ExpressionNode *assigned_expression = nullptr; }; -// 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_is_arg = true) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { String enum_name = p_info.class_name; @@ -758,7 +738,12 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio 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); + ScriptCodeCompletionOption option; + if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { + option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_CONSTANT); + } else { + option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE); + } r_result.insert(option.display, option); } if (p_suite->parent_block) { @@ -766,9 +751,11 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, } } -static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result); +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth); + +static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { + ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); -static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) { if (!p_parent_only) { bool outer = false; const GDScriptParser::ClassNode *clss = p_class; @@ -788,6 +775,9 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, continue; } option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); + if (member.constant->initializer) { + option.default_value = member.constant->initializer->reduced_value; + } break; case GDScriptParser::ClassNode::Member::CLASS: if (p_only_functions) { @@ -820,7 +810,6 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, break; case GDScriptParser::ClassNode::Member::SIGNAL: if (p_only_functions || outer) { - clss = clss->outer; continue; } option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL); @@ -840,10 +829,12 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, base_type.type = p_class->base_type; base_type.type.is_meta_type = p_static; - _find_identifiers_in_base(base_type, p_only_functions, r_result); + _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1); } -static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { + ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); + GDScriptParser::DataType base_type = p_base.type; bool _static = base_type.is_meta_type; @@ -856,7 +847,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base while (!base_type.has_no_type()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { - _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result); + _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1); // This already finds all parent identifiers, so we are done. base_type = GDScriptParser::DataType(); } break; @@ -914,7 +905,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } break; case GDScriptParser::DataType::NATIVE: { - StringName type = _get_real_class_name(base_type.native_type); + StringName type = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(type)) { return; } @@ -965,7 +956,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } break; case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); + Variant tmp; + Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return; } @@ -1007,19 +999,22 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } -static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { +static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { if (!p_only_functions && p_context.current_suite) { // This includes function parameters, since they are also locals. _find_identifiers_in_suite(p_context.current_suite, r_result); } if (p_context.current_class) { - _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result); + _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); } - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - MethodInfo function = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); - ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION); + 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 { @@ -1045,11 +1040,9 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool } static const char *_keywords[] = { - "and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert", - "breakpoint", "class", "extends", "is", "func", "preload", "signal", "tool", "await", - "const", "enum", "static", "super", "var", "break", "continue", "if", "elif", - "else", "for", "pass", "return", "match", "while", - 0 + "false", "PI", "TAU", "INF", "NAN", "self", "true", "breakpoint", "tool", "super", + "break", "continue", "pass", "return", + nullptr }; const char **kw = _keywords; @@ -1059,6 +1052,33 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool kw++; } + static const char *_keywords_with_space[] = { + "and", "in", "not", "or", "as", "class", "extends", "is", "func", "signal", "await", + "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while", + nullptr + }; + + 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", + nullptr + }; + + 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) { @@ -1250,8 +1270,8 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, r_type.type.builtin_type = GDScriptParser::get_builtin_type(call->function_name); found = true; break; - } else if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) { - MethodInfo mi = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name)); + } 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; @@ -1515,7 +1535,8 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, found = _guess_identifier_type_from_base(c, base, id, r_type); } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) { Callable::CallError err; - Variant base_val = Variant::construct(base.type.builtin_type, nullptr, 0, err); + Variant base_val; + Variant::construct(base.type.builtin_type, base_val, nullptr, 0, err); bool valid = false; Variant res = base_val.get(index.value, &valid); if (valid) { @@ -1552,9 +1573,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, Callable::CallError ce; bool v1_use_value = p1.value.get_type() != Variant::NIL && p1.value.get_type() != Variant::OBJECT; - Variant v1 = (v1_use_value) ? p1.value : Variant::construct(p1.type.builtin_type, nullptr, 0, ce); + Variant d1; + Variant::construct(p1.type.builtin_type, d1, nullptr, 0, ce); + Variant d2; + Variant::construct(p2.type.builtin_type, d2, nullptr, 0, ce); + + Variant v1 = (v1_use_value) ? p1.value : d1; bool v2_use_value = p2.value.get_type() != Variant::NIL && p2.value.get_type() != Variant::OBJECT; - Variant v2 = (v2_use_value) ? p2.value : Variant::construct(p2.type.builtin_type, nullptr, 0, ce); + Variant v2 = (v2_use_value) ? p2.value : d2; // avoid potential invalid ops if ((op->variant_op == Variant::OP_DIVIDE || op->variant_op == Variant::OP_MODULE) && v2.get_type() == Variant::INT) { v2 = 1; @@ -1724,15 +1750,15 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, return true; } } - base_type = base_type.class_type->base_type; } + base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::NATIVE: { 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); + StringName real_native = GDScriptParser::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()) { @@ -1803,7 +1829,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } // Check ClassDB. - StringName class_name = _get_real_class_name(p_identifier); + StringName class_name = GDScriptParser::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; @@ -1919,7 +1945,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { return false; } @@ -1944,7 +1970,8 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } break; case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); + Variant tmp; + Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return false; @@ -2081,7 +2108,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } } break; case GDScriptParser::DataType::NATIVE: { - StringName native = _get_real_class_name(base_type.native_type); + StringName native = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(native)) { return false; } @@ -2094,7 +2121,8 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } break; case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; - Variant tmp = Variant::construct(base_type.builtin_type, nullptr, 0, err); + Variant tmp; + Variant::construct(base_type.builtin_type, tmp, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return false; } @@ -2131,9 +2159,9 @@ static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_co r_result.insert(option.display, option); } } else { - for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { - if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM); + for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { + if (CoreConstants::get_global_constant_enum(i) == current_enum) { + ScriptCodeCompletionOption option(CoreConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM); r_result.insert(option.display, option); } } @@ -2177,7 +2205,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2208,10 +2236,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } r_arghint = _make_arguments_hint(info, p_argidx); - return; } - if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) { + if (p_argidx == 0 && ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node")) { // Get autoloads List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2249,7 +2276,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c case GDScriptParser::DataType::BUILTIN: { if (base.get_type() == Variant::NIL) { Callable::CallError err; - base = Variant::construct(base_type.builtin_type, nullptr, 0, err); + Variant::construct(base_type.builtin_type, base, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { return; } @@ -2292,17 +2319,12 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c GDScriptParser::DataType base_type; bool _static = false; const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_call); - GDScriptParser::Node::Type callee_type = GDScriptParser::Node::NONE; + GDScriptParser::Node::Type callee_type = call->get_callee_type(); GDScriptCompletionIdentifier connect_base; - if (GDScriptParser::get_builtin_function(call->function_name) < GDScriptFunctions::FUNC_MAX) { - MethodInfo info = GDScriptFunctions::get_info(GDScriptParser::get_builtin_function(call->function_name)); - - if ((info.name == "load" || info.name == "preload") && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { - _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); - } - + 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; } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { @@ -2399,6 +2421,11 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path Variant::get_constants_for_type(completion_context.builtin_type, &constants); for (const List<StringName>::Element *E = constants.front(); E != nullptr; E = E->next()) { ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); + bool valid = false; + Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E->get(), &valid); + if (valid) { + option.default_value = default_value; + } options.insert(option.display, option); } } break; @@ -2453,16 +2480,16 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) { - _find_identifiers(completion_context, false, options); + _find_identifiers(completion_context, false, options, 0); r_forced = true; break; } - if (!type.enumeration.empty()) { + 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); + _find_identifiers(completion_context, false, options, 0); r_forced = true; } } break; @@ -2470,7 +2497,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path is_function = true; [[fallthrough]]; case GDScriptParser::COMPLETION_IDENTIFIER: { - _find_identifiers(completion_context, is_function, options); + _find_identifiers(completion_context, is_function, options, 0); } break; case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: is_function = true; @@ -2484,7 +2511,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } - _find_identifiers_in_base(base, is_function, options); + _find_identifiers_in_base(base, is_function, options, 0); } } break; case GDScriptParser::COMPLETION_SUBSCRIPT: { @@ -2504,7 +2531,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path c.current_class = nullptr; } - _find_identifiers_in_base(base, false, options); + _find_identifiers_in_base(base, false, options, 0); } break; case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { if (!completion_context.current_class) { @@ -2530,7 +2557,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path // TODO: Improve this to only list types. if (found) { - _find_identifiers_in_base(base, false, options); + _find_identifiers_in_base(base, false, options, 0); } r_forced = true; } break; @@ -2563,7 +2590,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } - StringName class_name = _get_real_class_name(native_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type); if (!ClassDB::class_exists(class_name)) { break; } @@ -2651,7 +2678,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path if (!completion_context.current_class) { break; } - _find_identifiers_in_class(completion_context.current_class, true, false, true, options); + _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0); } break; } @@ -2792,7 +2819,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2870,7 +2897,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co v = v_ref; } else { Callable::CallError err; - v = Variant::construct(base_type.builtin_type, NULL, 0, err); + Variant::construct(base_type.builtin_type, v, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { break; } @@ -2925,13 +2952,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) { @@ -2973,6 +2998,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol is_function = true; [[fallthrough]]; } + case GDScriptParser::COMPLETION_CALL_ARGUMENTS: case GDScriptParser::COMPLETION_IDENTIFIER: { GDScriptParser::DataType base_type; if (context.current_class) { @@ -3050,7 +3076,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 != NULL) { + if (enumName != nullptr) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM; r_result.class_name = "@GlobalScope"; r_result.class_member = enumName; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index a4e37a79f8..78399114a5 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,1567 +30,7 @@ #include "gdscript_function.h" -#include "core/os/os.h" #include "gdscript.h" -#include "gdscript_functions.h" - -Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { - int address = p_address & ADDR_MASK; - - //sequential table (jump table generated by compiler) - switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) { - case ADDR_TYPE_SELF: { -#ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access self without instance."; - return nullptr; - } -#endif - return &self; - } break; - case ADDR_TYPE_CLASS: { - return &static_ref; - } break; - case ADDR_TYPE_MEMBER: { -#ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access member without instance."; - return nullptr; - } -#endif - //member indexing is O(1) - return &p_instance->members.write[address]; - } break; - case ADDR_TYPE_CLASS_CONSTANT: { - //todo change to index! - GDScript *s = p_script; -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); -#endif - const StringName *sn = &_global_names_ptr[address]; - - while (s) { - GDScript *o = s; - while (o) { - Map<StringName, Variant>::Element *E = o->constants.find(*sn); - if (E) { - return &E->get(); - } - o = o->_owner; - } - s = s->_base; - } - - ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug."); - } break; - case ADDR_TYPE_LOCAL_CONSTANT: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _constant_count, nullptr); -#endif - return &_constants_ptr[address]; - } break; - case ADDR_TYPE_STACK: - case ADDR_TYPE_STACK_VARIABLE: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _stack_size, nullptr); -#endif - return &p_stack[address]; - } break; - case ADDR_TYPE_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr); -#endif - return &GDScriptLanguage::get_singleton()->get_global_array()[address]; - } break; -#ifdef TOOLS_ENABLED - case ADDR_TYPE_NAMED_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _named_globals_count, nullptr); -#endif - StringName id = _named_globals_ptr[address]; - - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { - return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; - } else { - r_error = "Autoload singleton '" + String(id) + "' has been removed."; - return nullptr; - } - } break; -#endif - case ADDR_TYPE_NIL: { - return &nil; - } break; - } - - ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); - return nullptr; -} - -#ifdef DEBUG_ENABLED -static String _get_var_type(const Variant *p_var) { - String basestr; - - if (p_var->get_type() == Variant::OBJECT) { - bool was_freed; - Object *bobj = p_var->get_validated_object_with_check(was_freed); - if (!bobj) { - if (was_freed) { - basestr = "null instance"; - } else { - basestr = "previously freed"; - } - } else { - if (bobj->get_script_instance()) { - basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; - } else { - basestr = bobj->get_class(); - } - } - - } else { - basestr = Variant::get_type_name(p_var->get_type()); - } - - return basestr; -} -#endif // DEBUG_ENABLED - -String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { - String err_text; - - if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - int errorarg = p_err.argument; - // Handle the Object to Object case separately as we don't have further class details. -#ifdef DEBUG_ENABLED - if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { - err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; - } else -#endif // DEBUG_ENABLED - { - err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; - } - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - err_text = "Invalid call. Nonexistent " + p_where + "."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { - err_text = "Attempt to call " + p_where + " on a null instance."; - } else { - err_text = "Bug, call error: #" + itos(p_err.error); - } - - return err_text; -} - -#if defined(__GNUC__) -#define OPCODES_TABLE \ - static const void *switch_table_ops[] = { \ - &&OPCODE_OPERATOR, \ - &&OPCODE_EXTENDS_TEST, \ - &&OPCODE_IS_BUILTIN, \ - &&OPCODE_SET, \ - &&OPCODE_GET, \ - &&OPCODE_SET_NAMED, \ - &&OPCODE_GET_NAMED, \ - &&OPCODE_SET_MEMBER, \ - &&OPCODE_GET_MEMBER, \ - &&OPCODE_ASSIGN, \ - &&OPCODE_ASSIGN_TRUE, \ - &&OPCODE_ASSIGN_FALSE, \ - &&OPCODE_ASSIGN_TYPED_BUILTIN, \ - &&OPCODE_ASSIGN_TYPED_NATIVE, \ - &&OPCODE_ASSIGN_TYPED_SCRIPT, \ - &&OPCODE_CAST_TO_BUILTIN, \ - &&OPCODE_CAST_TO_NATIVE, \ - &&OPCODE_CAST_TO_SCRIPT, \ - &&OPCODE_CONSTRUCT, \ - &&OPCODE_CONSTRUCT_ARRAY, \ - &&OPCODE_CONSTRUCT_DICTIONARY, \ - &&OPCODE_CALL, \ - &&OPCODE_CALL_RETURN, \ - &&OPCODE_CALL_ASYNC, \ - &&OPCODE_CALL_BUILT_IN, \ - &&OPCODE_CALL_SELF, \ - &&OPCODE_CALL_SELF_BASE, \ - &&OPCODE_AWAIT, \ - &&OPCODE_AWAIT_RESUME, \ - &&OPCODE_JUMP, \ - &&OPCODE_JUMP_IF, \ - &&OPCODE_JUMP_IF_NOT, \ - &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ - &&OPCODE_RETURN, \ - &&OPCODE_ITERATE_BEGIN, \ - &&OPCODE_ITERATE, \ - &&OPCODE_ASSERT, \ - &&OPCODE_BREAKPOINT, \ - &&OPCODE_LINE, \ - &&OPCODE_END \ - }; \ - static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum."); - -#define OPCODE(m_op) \ - m_op: -#define OPCODE_WHILE(m_test) -#define OPCODES_END \ - OPSEXIT: -#define OPCODES_OUT \ - OPSOUT: -#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]] -#define OPCODE_SWITCH(m_test) DISPATCH_OPCODE; -#define OPCODE_BREAK goto OPSEXIT -#define OPCODE_OUT goto OPSOUT -#else -#define OPCODES_TABLE -#define OPCODE(m_op) case m_op: -#define OPCODE_WHILE(m_test) while (m_test) -#define OPCODES_END -#define OPCODES_OUT -#define DISPATCH_OPCODE continue -#define OPCODE_SWITCH(m_test) switch (m_test) -#define OPCODE_BREAK break -#define OPCODE_OUT break -#endif - -Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) { - OPCODES_TABLE; - - if (!_code_ptr) { - return Variant(); - } - - r_err.error = Callable::CallError::CALL_OK; - - Variant self; - Variant static_ref; - Variant retvalue; - Variant *stack = nullptr; - Variant **call_args; - int defarg = 0; - -#ifdef DEBUG_ENABLED - - //GDScriptLanguage::get_singleton()->calls++; - -#endif - - uint32_t alloca_size = 0; - GDScript *script; - int ip = 0; - int line = _initial_line; - - if (p_state) { - //use existing (supplied) state (awaited) - stack = (Variant *)p_state->stack.ptr(); - call_args = (Variant **)&p_state->stack.ptr()[sizeof(Variant) * p_state->stack_size]; //ptr() to avoid bounds check - line = p_state->line; - ip = p_state->ip; - alloca_size = p_state->stack.size(); - script = p_state->script; - p_instance = p_state->instance; - defarg = p_state->defarg; - self = p_state->self; - - } else { - if (p_argcount != _argument_count) { - if (p_argcount > _argument_count) { - r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_err.argument = _argument_count; - - return Variant(); - } else if (p_argcount < _argument_count - _default_arg_count) { - r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_err.argument = _argument_count - _default_arg_count; - return Variant(); - } else { - defarg = _argument_count - p_argcount; - } - } - - alloca_size = sizeof(Variant *) * _call_size + sizeof(Variant) * _stack_size; - - if (alloca_size) { - uint8_t *aptr = (uint8_t *)alloca(alloca_size); - - if (_stack_size) { - stack = (Variant *)aptr; - for (int i = 0; i < p_argcount; i++) { - if (!argument_types[i].has_type) { - memnew_placement(&stack[i], Variant(*p_args[i])); - continue; - } - - if (!argument_types[i].is_type(*p_args[i], true)) { - r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_err.argument = i; - r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; - return Variant(); - } - if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - Variant arg = Variant::construct(argument_types[i].builtin_type, &p_args[i], 1, r_err); - memnew_placement(&stack[i], Variant(arg)); - } else { - memnew_placement(&stack[i], Variant(*p_args[i])); - } - } - for (int i = p_argcount; i < _stack_size; i++) { - memnew_placement(&stack[i], Variant); - } - } else { - stack = nullptr; - } - - if (_call_size) { - call_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; - } else { - call_args = nullptr; - } - - } else { - stack = nullptr; - call_args = nullptr; - } - - if (p_instance) { - if (p_instance->base_ref && static_cast<Reference *>(p_instance->owner)->is_referenced()) { - self = REF(static_cast<Reference *>(p_instance->owner)); - } else { - self = p_instance->owner; - } - script = p_instance->script.ptr(); - } else { - script = _script; - } - } - - static_ref = script; - - String err_text; - -#ifdef DEBUG_ENABLED - - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line); - } - -#define GD_ERR_BREAK(m_cond) \ - { \ - if (unlikely(m_cond)) { \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \ - OPCODE_BREAK; \ - } \ - } - -#define CHECK_SPACE(m_space) \ - GD_ERR_BREAK((ip + m_space) > _code_size) - -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \ - if (unlikely(!m_v)) \ - OPCODE_BREAK; - -#else -#define GD_ERR_BREAK(m_cond) -#define CHECK_SPACE(m_space) -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); - -#endif - -#ifdef DEBUG_ENABLED - - uint64_t function_start_time = 0; - uint64_t function_call_time = 0; - - if (GDScriptLanguage::get_singleton()->profiling) { - function_start_time = OS::get_singleton()->get_ticks_usec(); - function_call_time = 0; - profile.call_count++; - profile.frame_call_count++; - } - bool exit_ok = false; - bool awaited = false; -#endif - -#ifdef DEBUG_ENABLED - OPCODE_WHILE(ip < _code_size) { - int last_opcode = _code_ptr[ip]; -#else - OPCODE_WHILE(true) { -#endif - - OPCODE_SWITCH(_code_ptr[ip]) { - OPCODE(OPCODE_OPERATOR) { - CHECK_SPACE(5); - - bool valid; - Variant::Operator op = (Variant::Operator)_code_ptr[ip + 1]; - GD_ERR_BREAK(op >= Variant::OP_MAX); - - GET_VARIANT_PTR(a, 2); - GET_VARIANT_PTR(b, 3); - GET_VARIANT_PTR(dst, 4); - -#ifdef DEBUG_ENABLED - - Variant ret; - Variant::evaluate(op, *a, *b, ret, valid); -#else - Variant::evaluate(op, *a, *b, *dst, valid); -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - if (ret.get_type() == Variant::STRING) { - //return a string when invalid with the error - err_text = ret; - err_text += " in operator '" + Variant::get_operator_name(op) + "'."; - } else { - err_text = "Invalid operands '" + Variant::get_type_name(a->get_type()) + "' and '" + Variant::get_type_name(b->get_type()) + "' in operator '" + Variant::get_operator_name(op) + "'."; - } - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 5; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_EXTENDS_TEST) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(a, 1); - GET_VARIANT_PTR(b, 2); - GET_VARIANT_PTR(dst, 3); - -#ifdef DEBUG_ENABLED - if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { - err_text = "Right operand of 'is' is not a class."; - OPCODE_BREAK; - } -#endif - - bool extends_ok = false; - if (a->get_type() == Variant::OBJECT && a->operator Object *() != nullptr) { -#ifdef DEBUG_ENABLED - bool was_freed; - Object *obj_A = a->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Left operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } - - Object *obj_B = b->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Right operand of 'is' is a previously freed instance."; - OPCODE_BREAK; - } -#else - - Object *obj_A = *a; - Object *obj_B = *b; -#endif // DEBUG_ENABLED - - GDScript *scr_B = Object::cast_to<GDScript>(obj_B); - - if (scr_B) { - //if B is a script, the only valid condition is that A has an instance which inherits from the script - //in other situation, this shoul return false. - - if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { - GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); - //bool found=false; - while (cmp) { - if (cmp == scr_B) { - //inherits from script, all ok - extends_ok = true; - break; - } - - cmp = cmp->_base; - } - } - - } else { - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(obj_B); - -#ifdef DEBUG_ENABLED - if (!nc) { - err_text = "Right operand of 'is' is not a class (type: '" + obj_B->get_class() + "')."; - OPCODE_BREAK; - } -#endif - extends_ok = ClassDB::is_parent_class(obj_A->get_class_name(), nc->get_name()); - } - } - - *dst = extends_ok; - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_IS_BUILTIN) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(value, 1); - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 2]; - GET_VARIANT_PTR(dst, 3); - - GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); - - *dst = value->get_type() == var_type; - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(index, 2); - GET_VARIANT_PTR(value, 3); - - bool valid; - dst->set(*index, *value, &valid); - -#ifdef DEBUG_ENABLED - if (!valid) { - String v = index->operator String(); - if (v != "") { - v = "'" + v + "'"; - } else { - v = "of type '" + _get_var_type(index) + "'"; - } - err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; - OPCODE_BREAK; - } -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(src, 1); - GET_VARIANT_PTR(index, 2); - GET_VARIANT_PTR(dst, 3); - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get(*index, &valid); -#else - *dst = src->get(*index, &valid); - -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - String v = index->operator String(); - if (v != "") { - v = "'" + v + "'"; - } else { - v = "of type '" + _get_var_type(index) + "'"; - } - err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET_NAMED) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(value, 3); - - int indexname = _code_ptr[ip + 2]; - - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; - dst->set_named(*index, *value, &valid); - -#ifdef DEBUG_ENABLED - if (!valid) { - String err_type; - err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; - OPCODE_BREAK; - } -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET_NAMED) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(src, 1); - GET_VARIANT_PTR(dst, 3); - - int indexname = _code_ptr[ip + 2]; - - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - - bool valid; -#ifdef DEBUG_ENABLED - //allow better error message in cases where src and dst are the same stack position - Variant ret = src->get_named(*index, &valid); - -#else - *dst = src->get_named(*index, &valid); -#endif -#ifdef DEBUG_ENABLED - if (!valid) { - if (src->has_method(*index)) { - err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "'). Did you mean '." + index->operator String() + "()' or funcref(obj, \"" + index->operator String() + "\") ?"; - } else { - err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "')."; - } - OPCODE_BREAK; - } - *dst = ret; -#endif - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_SET_MEMBER) { - CHECK_SPACE(3); - int indexname = _code_ptr[ip + 1]; - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - GET_VARIANT_PTR(src, 2); - - bool valid; -#ifndef DEBUG_ENABLED - ClassDB::set_property(p_instance->owner, *index, *src, &valid); -#else - bool ok = ClassDB::set_property(p_instance->owner, *index, *src, &valid); - if (!ok) { - err_text = "Internal error setting property: " + String(*index); - OPCODE_BREAK; - } else if (!valid) { - err_text = "Error setting property '" + String(*index) + "' with value of type " + Variant::get_type_name(src->get_type()) + "."; - OPCODE_BREAK; - } -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_GET_MEMBER) { - CHECK_SPACE(3); - int indexname = _code_ptr[ip + 1]; - GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); - const StringName *index = &_global_names_ptr[indexname]; - GET_VARIANT_PTR(dst, 2); - -#ifndef DEBUG_ENABLED - ClassDB::get_property(p_instance->owner, *index, *dst); -#else - bool ok = ClassDB::get_property(p_instance->owner, *index, *dst); - if (!ok) { - err_text = "Internal error getting property: " + String(*index); - OPCODE_BREAK; - } -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN) { - CHECK_SPACE(3); - GET_VARIANT_PTR(dst, 1); - GET_VARIANT_PTR(src, 2); - - *dst = *src; - - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TRUE) { - CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 1); - - *dst = true; - - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_FALSE) { - CHECK_SPACE(2); - GET_VARIANT_PTR(dst, 1); - - *dst = false; - - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - - Variant::Type var_type = (Variant::Type)_code_ptr[ip + 1]; - GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); - - if (src->get_type() != var_type) { -#ifdef DEBUG_ENABLED - if (Variant::can_convert_strict(src->get_type(), var_type)) { -#endif // DEBUG_ENABLED - Callable::CallError ce; - *dst = Variant::construct(var_type, const_cast<const Variant **>(&src), 1, ce); - } else { -#ifdef DEBUG_ENABLED - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; - OPCODE_BREAK; - } - } else { -#endif // DEBUG_ENABLED - *dst = *src; - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(type, 1); - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); - GD_ERR_BREAK(!nc); - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + nc->get_name() + "'."; - OPCODE_BREAK; - } - Object *src_obj = src->operator Object *(); - - if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { - err_text = "Trying to assign value of type '" + src_obj->get_class_name() + - "' to a variable of type '" + nc->get_name() + "'."; - OPCODE_BREAK; - } -#endif // DEBUG_ENABLED - *dst = *src; - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { - CHECK_SPACE(4); - GET_VARIANT_PTR(dst, 2); - GET_VARIANT_PTR(src, 3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(type, 1); - Script *base_type = Object::cast_to<Script>(type->operator Object *()); - - GD_ERR_BREAK(!base_type); - - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - - if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { - ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); - if (!scr_inst) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - - Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); - bool valid = false; - - while (src_type) { - if (src_type == base_type) { - valid = true; - break; - } - src_type = src_type->get_base_script().ptr(); - } - - if (!valid) { - err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } - } -#endif // DEBUG_ENABLED - - *dst = *src; - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_BUILTIN) { - CHECK_SPACE(4); - Variant::Type to_type = (Variant::Type)_code_ptr[ip + 1]; - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); - - Callable::CallError err; - *dst = Variant::construct(to_type, (const Variant **)&src, 1, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - err_text = "Invalid cast: could not convert value to '" + Variant::get_type_name(to_type) + "'."; - OPCODE_BREAK; - } -#endif - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_NATIVE) { - CHECK_SPACE(4); - GET_VARIANT_PTR(to_type, 1); - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); - GD_ERR_BREAK(!nc); - -#ifdef DEBUG_ENABLED - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Invalid cast: can't convert a non-object value to an object type."; - OPCODE_BREAK; - } -#endif - Object *src_obj = src->operator Object *(); - - if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { - *dst = Variant(); // invalid cast, assign NULL - } else { - *dst = *src; - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CAST_TO_SCRIPT) { - CHECK_SPACE(4); - GET_VARIANT_PTR(to_type, 1); - GET_VARIANT_PTR(src, 2); - GET_VARIANT_PTR(dst, 3); - - Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); - - GD_ERR_BREAK(!base_type); - -#ifdef DEBUG_ENABLED - if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { - err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; - OPCODE_BREAK; - } -#endif - - bool valid = false; - - if (src->get_type() != Variant::NIL && src->operator Object *() != nullptr) { - ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); - - if (scr_inst) { - Script *src_type = src->operator Object *()->get_script_instance()->get_script().ptr(); - - while (src_type) { - if (src_type == base_type) { - valid = true; - break; - } - src_type = src_type->get_base_script().ptr(); - } - } - } - - if (valid) { - *dst = *src; // Valid cast, copy the source object - } else { - *dst = Variant(); // invalid cast, assign NULL - } - - ip += 4; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT) { - CHECK_SPACE(2); - Variant::Type t = Variant::Type(_code_ptr[ip + 1]); - int argc = _code_ptr[ip + 2]; - CHECK_SPACE(argc + 2); - Variant **argptrs = call_args; - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, 3 + i); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, 3 + argc); - Callable::CallError err; - *dst = Variant::construct(t, (const Variant **)argptrs, argc, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs); - OPCODE_BREAK; - } -#endif - - ip += 4 + argc; - //construct a basic type - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT_ARRAY) { - CHECK_SPACE(1); - int argc = _code_ptr[ip + 1]; - Array array; //arrays are always shared - array.resize(argc); - CHECK_SPACE(argc + 2); - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, 2 + i); - array[i] = *v; - } - - GET_VARIANT_PTR(dst, 2 + argc); - - *dst = array; - - ip += 3 + argc; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { - CHECK_SPACE(1); - int argc = _code_ptr[ip + 1]; - Dictionary dict; //arrays are always shared - - CHECK_SPACE(argc * 2 + 2); - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(k, 2 + i * 2 + 0); - GET_VARIANT_PTR(v, 2 + i * 2 + 1); - dict[*k] = *v; - } - - GET_VARIANT_PTR(dst, 2 + argc * 2); - - *dst = dict; - - ip += 3 + argc * 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_ASYNC) - OPCODE(OPCODE_CALL_RETURN) - OPCODE(OPCODE_CALL) { - CHECK_SPACE(4); - bool call_ret = _code_ptr[ip] != OPCODE_CALL; -#ifdef DEBUG_ENABLED - bool call_async = _code_ptr[ip] == OPCODE_CALL_ASYNC; -#endif - - int argc = _code_ptr[ip + 1]; - GET_VARIANT_PTR(base, 2); - int nameg = _code_ptr[ip + 3]; - - GD_ERR_BREAK(nameg < 0 || nameg >= _global_names_count); - const StringName *methodname = &_global_names_ptr[nameg]; - - GD_ERR_BREAK(argc < 0); - ip += 4; - CHECK_SPACE(argc + 1); - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i); - argptrs[i] = v; - } - -#ifdef DEBUG_ENABLED - uint64_t call_time = 0; - - if (GDScriptLanguage::get_singleton()->profiling) { - call_time = OS::get_singleton()->get_ticks_usec(); - } - -#endif - Callable::CallError err; - if (call_ret) { - GET_VARIANT_PTR(ret, argc); - base->call_ptr(*methodname, (const Variant **)argptrs, argc, ret, err); -#ifdef DEBUG_ENABLED - if (!call_async && ret->get_type() == Variant::OBJECT) { - // Check if getting a function state without await. - bool was_freed = false; - Object *obj = ret->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Got a freed object as a result of the call."; - OPCODE_BREAK; - } - if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { - err_text = R"(Trying to call an async function without "await".)"; - OPCODE_BREAK; - } - } -#endif - } else { - base->call_ptr(*methodname, (const Variant **)argptrs, argc, nullptr, err); - } -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->profiling) { - function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; - } - - if (err.error != Callable::CallError::CALL_OK) { - String methodstr = *methodname; - String basestr = _get_var_type(base); - - if (methodstr == "call") { - if (argc >= 1) { - methodstr = String(*argptrs[0]) + " (via call)"; - if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - err.argument += 1; - } - } - } else if (methodstr == "free") { - if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - if (base->is_ref()) { - err_text = "Attempted to free a reference."; - OPCODE_BREAK; - } else if (base->get_type() == Variant::OBJECT) { - err_text = "Attempted to free a locked object (calling or emitting)."; - OPCODE_BREAK; - } - } - } - err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); - OPCODE_BREAK; - } -#endif - - //_call_func(nullptr,base,*methodname,ip,argc,p_instance,stack); - ip += argc + 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_BUILT_IN) { - CHECK_SPACE(4); - - GDScriptFunctions::Function func = GDScriptFunctions::Function(_code_ptr[ip + 1]); - int argc = _code_ptr[ip + 2]; - GD_ERR_BREAK(argc < 0); - - ip += 3; - CHECK_SPACE(argc + 1); - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, argc); - - Callable::CallError err; - - GDScriptFunctions::call(func, (const Variant **)argptrs, argc, *dst, err); - -#ifdef DEBUG_ENABLED - if (err.error != Callable::CallError::CALL_OK) { - String methodstr = GDScriptFunctions::get_func_name(func); - if (dst->get_type() == Variant::STRING) { - //call provided error string - err_text = "Error calling built-in function '" + methodstr + "': " + String(*dst); - } else { - err_text = _get_call_error(err, "built-in function '" + methodstr + "'", (const Variant **)argptrs); - } - OPCODE_BREAK; - } -#endif - ip += argc + 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_CALL_SELF) { - OPCODE_BREAK; - } - - OPCODE(OPCODE_CALL_SELF_BASE) { - CHECK_SPACE(2); - int self_fun = _code_ptr[ip + 1]; - -#ifdef DEBUG_ENABLED - if (self_fun < 0 || self_fun >= _global_names_count) { - err_text = "compiler bug, function name not found"; - OPCODE_BREAK; - } -#endif - const StringName *methodname = &_global_names_ptr[self_fun]; - - int argc = _code_ptr[ip + 2]; - - CHECK_SPACE(2 + argc + 1); - - Variant **argptrs = call_args; - - for (int i = 0; i < argc; i++) { - GET_VARIANT_PTR(v, i + 3); - argptrs[i] = v; - } - - GET_VARIANT_PTR(dst, argc + 3); - - const GDScript *gds = _script; - - const Map<StringName, GDScriptFunction *>::Element *E = nullptr; - while (gds->base.ptr()) { - gds = gds->base.ptr(); - E = gds->member_functions.find(*methodname); - if (E) { - break; - } - } - - Callable::CallError err; - - if (E) { - *dst = E->get()->call(p_instance, (const Variant **)argptrs, argc, err); - } else if (gds->native.ptr()) { - if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { - MethodBind *mb = ClassDB::get_method(gds->native->get_name(), *methodname); - if (!mb) { - err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - } else { - *dst = mb->call(p_instance->owner, (const Variant **)argptrs, argc, err); - } - } else { - err.error = Callable::CallError::CALL_OK; - } - } else { - if (*methodname != GDScriptLanguage::get_singleton()->strings._init) { - err.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - } else { - err.error = Callable::CallError::CALL_OK; - } - } - - if (err.error != Callable::CallError::CALL_OK) { - String methodstr = *methodname; - err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); - - OPCODE_BREAK; - } - - ip += 4 + argc; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_AWAIT) { - int ipofs = 2; - CHECK_SPACE(3); - - //do the oneshot connect - GET_VARIANT_PTR(argobj, 1); - - Signal sig; - bool is_signal = true; - - { - Variant result = *argobj; - - if (argobj->get_type() == Variant::OBJECT) { - bool was_freed = false; - Object *obj = argobj->get_validated_object_with_check(was_freed); - - if (was_freed) { - err_text = "Trying to await on a freed object."; - OPCODE_BREAK; - } - - // Is this even possible to be null at this point? - if (obj) { - if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { - static StringName completed = _scs_create("completed"); - result = Signal(obj, completed); - } - } - } - - if (result.get_type() != Variant::SIGNAL) { - ip += 4; // Skip OPCODE_AWAIT_RESUME and its data. - // The stack pointer should be the same, so we don't need to set a return value. - is_signal = false; - } else { - sig = result; - } - } - - if (is_signal) { - Ref<GDScriptFunctionState> gdfs = memnew(GDScriptFunctionState); - gdfs->function = this; - - gdfs->state.stack.resize(alloca_size); - //copy variant stack - for (int i = 0; i < _stack_size; i++) { - memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); - } - gdfs->state.stack_size = _stack_size; - gdfs->state.self = self; - gdfs->state.alloca_size = alloca_size; - gdfs->state.ip = ip + ipofs; - gdfs->state.line = line; - gdfs->state.script = _script; - { - MutexLock lock(GDScriptLanguage::get_singleton()->lock); - _script->pending_func_states.add(&gdfs->scripts_list); - if (p_instance) { - gdfs->state.instance = p_instance; - p_instance->pending_func_states.add(&gdfs->instances_list); - } else { - gdfs->state.instance = nullptr; - } - } -#ifdef DEBUG_ENABLED - gdfs->state.function_name = name; - gdfs->state.script_path = _script->get_path(); -#endif - gdfs->state.defarg = defarg; - gdfs->function = this; - - retvalue = gdfs; - - Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT); - if (err != OK) { - err_text = "Error connecting to signal: " + sig.get_name() + " during await."; - OPCODE_BREAK; - } - -#ifdef DEBUG_ENABLED - exit_ok = true; - awaited = true; -#endif - OPCODE_BREAK; - } - } - DISPATCH_OPCODE; // Needed for synchronous calls (when result is immediately available). - - OPCODE(OPCODE_AWAIT_RESUME) { - CHECK_SPACE(2); -#ifdef DEBUG_ENABLED - if (!p_state) { - err_text = ("Invalid Resume (bug?)"); - OPCODE_BREAK; - } -#endif - GET_VARIANT_PTR(result, 1); - *result = p_state->result; - ip += 2; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP) { - CHECK_SPACE(2); - int to = _code_ptr[ip + 1]; - - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_IF) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(test, 1); - - bool result = test->booleanize(); - - if (result) { - int to = _code_ptr[ip + 2]; - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } else { - ip += 3; - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_IF_NOT) { - CHECK_SPACE(3); - - GET_VARIANT_PTR(test, 1); - - bool result = test->booleanize(); - - if (!result) { - int to = _code_ptr[ip + 2]; - GD_ERR_BREAK(to < 0 || to > _code_size); - ip = to; - } else { - ip += 3; - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_JUMP_TO_DEF_ARGUMENT) { - CHECK_SPACE(2); - ip = _default_arg_ptr[defarg]; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_RETURN) { - CHECK_SPACE(2); - GET_VARIANT_PTR(r, 1); - retvalue = *r; -#ifdef DEBUG_ENABLED - exit_ok = true; -#endif - OPCODE_BREAK; - } - - OPCODE(OPCODE_ITERATE_BEGIN) { - CHECK_SPACE(8); //space for this a regular iterate - - GET_VARIANT_PTR(counter, 1); - GET_VARIANT_PTR(container, 2); - - bool valid; - if (!container->iter_init(*counter, valid)) { -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'."; - OPCODE_BREAK; - } -#endif - int jumpto = _code_ptr[ip + 3]; - GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); - ip = jumpto; - } else { - GET_VARIANT_PTR(iterator, 4); - - *iterator = container->iter_get(*counter, valid); -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'."; - OPCODE_BREAK; - } -#endif - ip += 5; //skip regular iterate which is always next - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ITERATE) { - CHECK_SPACE(4); - - GET_VARIANT_PTR(counter, 1); - GET_VARIANT_PTR(container, 2); - - bool valid; - if (!container->iter_next(*counter, valid)) { -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; - OPCODE_BREAK; - } -#endif - int jumpto = _code_ptr[ip + 3]; - GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); - ip = jumpto; - } else { - GET_VARIANT_PTR(iterator, 4); - - *iterator = container->iter_get(*counter, valid); -#ifdef DEBUG_ENABLED - if (!valid) { - err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; - OPCODE_BREAK; - } -#endif - ip += 5; //loop again - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_ASSERT) { - CHECK_SPACE(3); - -#ifdef DEBUG_ENABLED - GET_VARIANT_PTR(test, 1); - bool result = test->booleanize(); - - if (!result) { - String message_str; - if (_code_ptr[ip + 2] != 0) { - GET_VARIANT_PTR(message, 2); - message_str = *message; - } - if (message_str.empty()) { - err_text = "Assertion failed."; - } else { - err_text = "Assertion failed: " + message_str; - } - OPCODE_BREAK; - } - -#endif - ip += 3; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_BREAKPOINT) { -#ifdef DEBUG_ENABLED - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true); - } -#endif - ip += 1; - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_LINE) { - CHECK_SPACE(2); - - line = _code_ptr[ip + 1]; - ip += 2; - - if (EngineDebugger::is_active()) { - // line - bool do_break = false; - - if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) { - if (EngineDebugger::get_script_debugger()->get_depth() <= 0) { - EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1); - } - if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0) { - do_break = true; - } - } - - if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source)) { - do_break = true; - } - - if (do_break) { - GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true); - } - - EngineDebugger::get_singleton()->line_poll(); - } - } - DISPATCH_OPCODE; - - OPCODE(OPCODE_END) { -#ifdef DEBUG_ENABLED - exit_ok = true; -#endif - OPCODE_BREAK; - } - -#if 0 // Enable for debugging. - default: { - err_text = "Illegal opcode " + itos(_code_ptr[ip]) + " at address " + itos(ip); - OPCODE_BREAK; - } -#endif - } - - OPCODES_END -#ifdef DEBUG_ENABLED - if (exit_ok) { - OPCODE_OUT; - } - //error - // function, file, line, error, explanation - String err_file; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->path != "") { - err_file = p_instance->script->path; - } else if (script) { - err_file = script->path; - } - if (err_file == "") { - err_file = "<built-in>"; - } - String err_func = name; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->name != "") { - err_func = p_instance->script->name + "." + err_func; - } - int err_line = line; - if (err_text == "") { - err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please)."; - } - - if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) { - // debugger break did not happen - - _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT); - } - -#endif - OPCODE_OUT; - } - - OPCODES_OUT -#ifdef DEBUG_ENABLED - if (GDScriptLanguage::get_singleton()->profiling) { - uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time; - profile.total_time += time_taken; - profile.self_time += time_taken - function_call_time; - profile.frame_total_time += time_taken; - profile.frame_self_time += time_taken - function_call_time; - GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; - } - - // Check if this is the last time the function is resuming from await - // Will be true if never awaited as well - // When it's the last resume it will postpone the exit from stack, - // so the debugger knows which function triggered the resume of the next function (if any) - if (!p_state || awaited) { - if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->exit_function(); - } -#endif - - if (_stack_size) { - //free stack - for (int i = 0; i < _stack_size; i++) { - stack[i].~Variant(); - } - } - -#ifdef DEBUG_ENABLED - } -#endif - - return retvalue; -} const int *GDScriptFunction::get_code() const { return _code_ptr; @@ -1637,14 +77,14 @@ int GDScriptFunction::get_max_stack_size() const { } struct _GDFKC { - int order; + int order = 0; List<int> pos; }; struct _GDFKCS { - int order; + int order = 0; StringName id; - int pos; + int pos = 0; bool operator<(const _GDFKCS &p_r) const { return order < p_r.order; @@ -1674,7 +114,7 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String 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); } } @@ -1699,35 +139,21 @@ 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 } GDScriptFunction::~GDScriptFunction() { + for (int i = 0; i < lambdas.size(); i++) { + memdelete(lambdas[i]); + } + #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->lock); @@ -1822,7 +248,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { bool completed = true; // If the return value is a GDScriptFunctionState reference, - // then the function did awaited again after resuming. + // then the function did await again after resuming. if (ret.is_ref()) { GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); if (gdfs && gdfs->function == function) { @@ -1872,7 +298,6 @@ void GDScriptFunctionState::_bind_methods() { GDScriptFunctionState::GDScriptFunctionState() : scripts_list(this), instances_list(this) { - function = nullptr; } GDScriptFunctionState::~GDScriptFunctionState() { diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 771baf6a08..70b62ced6d 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,18 +31,23 @@ #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 { +class GDScriptDataType { +private: + GDScriptDataType *container_element_type = nullptr; + +public: enum Kind { UNINITIALIZED, BUILTIN, @@ -56,7 +61,8 @@ struct GDScriptDataType { bool has_type = false; Variant::Type builtin_type = Variant::NIL; StringName native_type; - Ref<Script> script_type; + Script *script_type = nullptr; + Ref<Script> script_type_ref; bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const { if (!has_type) { @@ -69,7 +75,24 @@ struct GDScriptDataType { case BUILTIN: { Variant::Type var_type = p_variant.get_type(); bool valid = builtin_type == var_type; - if (!valid && p_allow_implicit_conversion) { + if (valid && builtin_type == Variant::ARRAY && has_container_element_type()) { + Array array = p_variant; + if (array.is_typed()) { + Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin(); + StringName array_native_type = array.get_typed_class_name(); + Ref<Script> array_script_type_ref = array.get_typed_script(); + + if (array_script_type_ref.is_valid()) { + valid = (container_element_type->kind == SCRIPT || container_element_type->kind == GDSCRIPT) && container_element_type->script_type == array_script_type_ref.ptr(); + } else if (array_native_type != StringName()) { + valid = container_element_type->kind == NATIVE && container_element_type->native_type == array_native_type; + } else { + valid = container_element_type->kind == BUILTIN && container_element_type->builtin_type == array_builtin_type; + } + } else { + valid = false; + } + } else if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(var_type, builtin_type); } return valid; @@ -151,48 +174,218 @@ struct GDScriptDataType { return info; } - GDScriptDataType() {} + void set_container_element_type(const GDScriptDataType &p_element_type) { + container_element_type = memnew(GDScriptDataType(p_element_type)); + } + + GDScriptDataType get_container_element_type() const { + ERR_FAIL_COND_V(container_element_type == nullptr, GDScriptDataType()); + return *container_element_type; + } + + bool has_container_element_type() const { + return container_element_type != nullptr; + } + + void unset_container_element_type() { + if (container_element_type) { + memdelete(container_element_type); + } + container_element_type = nullptr; + } + + GDScriptDataType() = default; + + GDScriptDataType &operator=(const GDScriptDataType &p_other) { + kind = p_other.kind; + has_type = p_other.has_type; + builtin_type = p_other.builtin_type; + native_type = p_other.native_type; + script_type = p_other.script_type; + script_type_ref = p_other.script_type_ref; + unset_container_element_type(); + if (p_other.has_container_element_type()) { + set_container_element_type(p_other.get_container_element_type()); + } + return *this; + } + + GDScriptDataType(const GDScriptDataType &p_other) { + *this = p_other; + } + + ~GDScriptDataType() { + unset_container_element_type(); + } }; 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, OPCODE_ASSIGN_TRUE, OPCODE_ASSIGN_FALSE, OPCODE_ASSIGN_TYPED_BUILTIN, + OPCODE_ASSIGN_TYPED_ARRAY, OPCODE_ASSIGN_TYPED_NATIVE, OPCODE_ASSIGN_TYPED_SCRIPT, 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_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, - OPCODE_CALL_BUILT_IN, - OPCODE_CALL_SELF, + 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, + // 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_CREATE_LAMBDA, OPCODE_JUMP, OPCODE_JUMP_IF, OPCODE_JUMP_IF_NOT, OPCODE_JUMP_TO_DEF_ARGUMENT, OPCODE_RETURN, + OPCODE_RETURN_TYPED_BUILTIN, + OPCODE_RETURN_TYPED_ARRAY, + OPCODE_RETURN_TYPED_NATIVE, + OPCODE_RETURN_TYPED_SCRIPT, 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_STORE_NAMED_GLOBAL, + OPCODE_TYPE_ADJUST_BOOL, + OPCODE_TYPE_ADJUST_INT, + OPCODE_TYPE_ADJUST_FLOAT, + OPCODE_TYPE_ADJUST_STRING, + OPCODE_TYPE_ADJUST_VECTOR2, + OPCODE_TYPE_ADJUST_VECTOR2I, + OPCODE_TYPE_ADJUST_RECT2, + OPCODE_TYPE_ADJUST_RECT2I, + OPCODE_TYPE_ADJUST_VECTOR3, + OPCODE_TYPE_ADJUST_VECTOR3I, + OPCODE_TYPE_ADJUST_TRANSFORM2D, + OPCODE_TYPE_ADJUST_PLANE, + OPCODE_TYPE_ADJUST_QUAT, + OPCODE_TYPE_ADJUST_AABB, + OPCODE_TYPE_ADJUST_BASIS, + OPCODE_TYPE_ADJUST_TRANSFORM, + OPCODE_TYPE_ADJUST_COLOR, + OPCODE_TYPE_ADJUST_STRING_NAME, + OPCODE_TYPE_ADJUST_NODE_PATH, + OPCODE_TYPE_ADJUST_RID, + OPCODE_TYPE_ADJUST_OBJECT, + OPCODE_TYPE_ADJUST_CALLABLE, + OPCODE_TYPE_ADJUST_SIGNAL, + OPCODE_TYPE_ADJUST_DICTIONARY, + OPCODE_TYPE_ADJUST_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, OPCODE_ASSERT, OPCODE_BREAKPOINT, OPCODE_LINE, @@ -203,16 +396,24 @@ public: ADDR_BITS = 24, ADDR_MASK = ((1 << ADDR_BITS) - 1), ADDR_TYPE_MASK = ~ADDR_MASK, - ADDR_TYPE_SELF = 0, - ADDR_TYPE_CLASS = 1, + ADDR_TYPE_STACK = 0, + ADDR_TYPE_CONSTANT = 1, ADDR_TYPE_MEMBER = 2, - ADDR_TYPE_CLASS_CONSTANT = 3, - ADDR_TYPE_LOCAL_CONSTANT = 4, - ADDR_TYPE_STACK = 5, - ADDR_TYPE_STACK_VARIABLE = 6, - ADDR_TYPE_GLOBAL = 7, - ADDR_TYPE_NAMED_GLOBAL = 8, - ADDR_TYPE_NIL = 9 + }; + + enum FixedAddresses { + ADDR_STACK_SELF = 0, + ADDR_STACK_CLASS = 1, + ADDR_STACK_NIL = 2, + ADDR_SELF = ADDR_STACK_SELF | (ADDR_TYPE_STACK << ADDR_BITS), + ADDR_CLASS = ADDR_STACK_CLASS | (ADDR_TYPE_STACK << ADDR_BITS), + ADDR_NIL = ADDR_STACK_NIL | (ADDR_TYPE_STACK << ADDR_BITS), + }; + + enum Instruction { + INSTR_BITS = 20, + INSTR_MASK = ((1 << INSTR_BITS) - 1), + INSTR_ARGS_MASK = ~INSTR_MASK, }; struct StackDebug { @@ -224,88 +425,123 @@ 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; + int _lambdas_count = 0; + GDScriptFunction **_lambdas_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<GDScriptFunction *> lambdas; 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; - _FORCE_INLINE_ Variant *_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const; + _FORCE_INLINE_ Variant *_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const; _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; 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 { - GDScript *script; - GDScriptInstance *instance; + GDScript *script = nullptr; + GDScriptInstance *instance = nullptr; #ifdef DEBUG_ENABLED StringName function_name; String script_path; #endif Vector<uint8_t> stack; - int stack_size; - Variant self; - uint32_t alloca_size; - int ip; - int line; - int defarg; + int stack_size = 0; + uint32_t alloca_size = 0; + int ip = 0; + int line = 0; + int defarg = 0; Variant result; }; @@ -341,9 +577,18 @@ public: ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), Variant()); return default_arguments[p_idx]; } +#ifdef TOOLS_ENABLED + const Vector<Variant> &get_default_arg_values() const { + return default_arg_values; + } +#endif // TOOLS_ENABLED Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr); +#ifdef DEBUG_ENABLED + void disassemble(const Vector<String> &p_code_lines) const; +#endif + _FORCE_INLINE_ MultiplayerAPI::RPCMode get_rpc_mode() const { return rpc_mode; } GDScriptFunction(); ~GDScriptFunction(); @@ -352,7 +597,7 @@ public: class GDScriptFunctionState : public Reference { GDCLASS(GDScriptFunctionState, Reference); friend class GDScriptFunction; - GDScriptFunction *function; + GDScriptFunction *function = nullptr; GDScriptFunction::CallState state; Variant _signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error); Ref<GDScriptFunctionState> first_state; diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp deleted file mode 100644 index fefbf906f0..0000000000 --- a/modules/gdscript/gdscript_functions.cpp +++ /dev/null @@ -1,1964 +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_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_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_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; - } - - } 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); - - 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()]; - } - } - - } 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, "s")); - 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_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp new file mode 100644 index 0000000000..0bc109b6e1 --- /dev/null +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* gdscript_lambda_callable.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_lambda_callable.h" + +#include "core/templates/hashfuncs.h" +#include "gdscript.h" + +bool GDScriptLambdaCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + // Lambda callables are only compared by reference. + return p_a == p_b; +} + +bool GDScriptLambdaCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + // Lambda callables are only compared by reference. + return p_a < p_b; +} + +uint32_t GDScriptLambdaCallable::hash() const { + return h; +} + +String GDScriptLambdaCallable::get_as_text() const { + if (function->get_name() != StringName()) { + return function->get_name().operator String() + "(lambda)"; + } + return "(anonymous lambda)"; +} + +CallableCustom::CompareEqualFunc GDScriptLambdaCallable::get_compare_equal_func() const { + return compare_equal; +} + +CallableCustom::CompareLessFunc GDScriptLambdaCallable::get_compare_less_func() const { + return compare_less; +} + +ObjectID GDScriptLambdaCallable::get_object() const { + return script->get_instance_id(); +} + +void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + int captures_amount = captures.size(); + + if (captures_amount > 0) { + Vector<const Variant *> args; + args.resize(p_argcount + captures_amount); + for (int i = 0; i < captures_amount; i++) { + args.write[i] = &captures[i]; + } + for (int i = 0; i < p_argcount; i++) { + args.write[i + captures_amount] = p_arguments[i]; + } + + r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error); + r_call_error.argument -= captures_amount; + } else { + r_return_value = function->call(nullptr, p_arguments, p_argcount, r_call_error); + } +} + +GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + script = p_script; + function = p_function; + captures = p_captures; + + h = (uint32_t)hash_djb2_one_64((uint64_t)this); +} diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_lambda_callable.h index 2c6dc02913..357c845250 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* gdscript_functions.h */ +/* gdscript_lambda_callable.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,38 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GDSCRIPT_FUNCTIONS_H -#define GDSCRIPT_FUNCTIONS_H +#ifndef GDSCRIPT_LAMBDA_CALLABLE +#define GDSCRIPT_LAMBDA_CALLABLE -#include "core/variant.h" +#include "core/object/reference.h" +#include "core/templates/vector.h" +#include "core/variant/callable.h" +#include "core/variant/variant.h" + +class GDScript; +class GDScriptFunction; +class GDScriptInstance; + +class GDScriptLambdaCallable : public CallableCustom { + GDScriptFunction *function = nullptr; + Ref<GDScript> script; + uint32_t h; + + Vector<Variant> captures; + + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); -class GDScriptFunctions { 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 - }; + uint32_t hash() const override; + String get_as_text() const override; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + ObjectID get_object() const override; + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; - 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); + GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures); + virtual ~GDScriptLambdaCallable() = default; }; -#endif // GDSCRIPT_FUNCTIONS_H +#endif // GDSCRIPT_LAMBDA_CALLABLE diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index aeec1c0379..f9027c3a87 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,15 +30,15 @@ #include "gdscript_parser.h" +#include "core/config/project_settings.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" #include "core/os/file_access.h" -#include "core/project_settings.h" #include "gdscript.h" #ifdef DEBUG_ENABLED #include "core/os/os.h" -#include "core/string_builder.h" +#include "core/string/string_builder.h" #endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED @@ -47,7 +47,7 @@ static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.empty()) { + if (builtin_types.is_empty()) { builtin_types["bool"] = Variant::BOOL; builtin_types["int"] = Variant::INT; builtin_types["float"] = Variant::FLOAT; @@ -65,7 +65,7 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { builtin_types["Basis"] = Variant::BASIS; builtin_types["Transform"] = Variant::TRANSFORM; builtin_types["Color"] = Variant::COLOR; - builtin_types["RID"] = Variant::_RID; + builtin_types["RID"] = Variant::RID; builtin_types["Object"] = Variant::OBJECT; builtin_types["StringName"] = Variant::STRING_NAME; builtin_types["NodePath"] = Variant::NODE_PATH; @@ -94,17 +94,43 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } -void GDScriptParser::cleanup() { - builtin_types.clear(); -} - -GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringName &p_name) { - for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { - if (p_name == GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) { - return GDScriptFunctions::Function(i); +// 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, +}; +StringName GDScriptParser::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++; } } - return GDScriptFunctions::FUNC_MAX; + if (underscore_map.has(p_source)) { + return underscore_map[p_source]; + } + return p_source; +} + +void GDScriptParser::cleanup() { + builtin_types.clear(); + underscore_map.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { @@ -118,12 +144,11 @@ void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const 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"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, 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>); @@ -139,8 +164,10 @@ GDScriptParser::GDScriptParser() { 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_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, 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>); + register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, 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>); @@ -184,23 +211,25 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { #ifdef DEBUG_ENABLED void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { + ERR_FAIL_COND(p_source == nullptr); Vector<String> symbols; - if (!p_symbol1.empty()) { + if (!p_symbol1.is_empty()) { symbols.push_back(p_symbol1); } - if (!p_symbol2.empty()) { + if (!p_symbol2.is_empty()) { symbols.push_back(p_symbol2); } - if (!p_symbol3.empty()) { + if (!p_symbol3.is_empty()) { symbols.push_back(p_symbol3); } - if (!p_symbol4.empty()) { + if (!p_symbol4.is_empty()) { symbols.push_back(p_symbol4); } push_warning(p_source, p_code, symbols); } 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; } @@ -291,7 +320,7 @@ void GDScriptParser::pop_completion_call() { if (!for_completion) { return; } - ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to pop empty completion call stack"); + ERR_FAIL_COND_MSG(completion_call_stack.is_empty(), "Trying to pop empty completion call stack"); completion_call_stack.pop_back(); } @@ -299,7 +328,7 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) { if (!for_completion || passed_cursor) { return; } - ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to set argument on empty completion call stack"); + 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; } @@ -327,7 +356,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ bool found = false; const String &line = lines[i]; for (int j = 0; j < line.size(); j++) { - if (line[j] == CharType(0xFFFF)) { + if (line[j] == char32_t(0xFFFF)) { found = true; break; } else if (line[j] == '\t') { @@ -365,7 +394,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ } #endif - if (errors.empty()) { + if (errors.is_empty()) { return OK; } else { return ERR_PARSE_ERROR; @@ -373,10 +402,12 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ } GDScriptTokenizer::Token GDScriptParser::advance() { + lambda_ended = false; // Empty marker since we're past the end in any case. + if (current.type == GDScriptTokenizer::Token::TK_EOF) { ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream."); } - if (for_completion && !completion_call_stack.empty()) { + if (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; @@ -399,7 +430,7 @@ bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) { return true; } -bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) { +bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) const { if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) { return current.is_identifier(); } @@ -414,7 +445,7 @@ bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const return false; } -bool GDScriptParser::is_at_end() { +bool GDScriptParser::is_at_end() const { return check(GDScriptTokenizer::Token::TK_EOF); } @@ -465,16 +496,34 @@ void GDScriptParser::pop_multiline() { tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false); } -bool GDScriptParser::is_statement_end() { +bool GDScriptParser::is_statement_end_token() const { return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF); } +bool GDScriptParser::is_statement_end() const { + return lambda_ended || in_lambda || is_statement_end_token(); +} + void GDScriptParser::end_statement(const String &p_context) { bool found = false; while (is_statement_end() && !is_at_end()) { // Remove sequential newlines/semicolons. + if (is_statement_end_token()) { + // Only consume if this is an actual token. + advance(); + } else if (lambda_ended) { + lambda_ended = false; // Consume this "token". + found = true; + break; + } else { + if (!found) { + lambda_ended = true; // Mark the lambda as done since we found something else to end the statement. + found = true; + } + break; + } + 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())); @@ -507,7 +556,7 @@ void GDScriptParser::parse_program() { // Order here doesn't matter, but there should be only one of each at most. switch (current.type) { case GDScriptTokenizer::Token::CLASS_NAME: - if (!annotation_stack.empty()) { + if (!annotation_stack.is_empty()) { push_error(R"("class_name" should be used before annotations.)"); } advance(); @@ -518,7 +567,7 @@ void GDScriptParser::parse_program() { } break; case GDScriptTokenizer::Token::EXTENDS: - if (!annotation_stack.empty()) { + if (!annotation_stack.is_empty()) { push_error(R"("extends" should be used before annotations.)"); } advance(); @@ -556,6 +605,17 @@ void GDScriptParser::parse_program() { parse_class_body(); +#ifdef TOOLS_ENABLED + for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) { + if (E->get().new_line && E->get().comment.begins_with("##")) { + class_doc_line = MIN(class_doc_line, E->key()); + } + } + if (has_comment(class_doc_line)) { + get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false); + } +#endif // TOOLS_ENABLED + if (!check(GDScriptTokenizer::Token::TK_EOF)) { push_error("Expected end of file."); } @@ -586,6 +646,14 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { return n_class; } + if (match(GDScriptTokenizer::Token::EXTENDS)) { + if (n_class->extends_used) { + push_error(R"(Cannot use "extends" more than once in the same class.)"); + } + parse_extends(); + end_statement("superclass"); + } + parse_class_body(); consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); @@ -631,7 +699,6 @@ void GDScriptParser::parse_extends() { current_class->extends_path = previous.literal; if (!match(GDScriptTokenizer::Token::PERIOD)) { - end_statement("superclass path"); return; } } @@ -659,11 +726,14 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() if (member == nullptr) { return; } +#ifdef TOOLS_ENABLED + int doc_comment_line = member->start_line - 1; +#endif // TOOLS_ENABLED + // Consume annotations. - while (!annotation_stack.empty()) { + 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 { @@ -671,7 +741,25 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() clear_unused_annotations(); return; } +#ifdef TOOLS_ENABLED + if (last_annotation->start_line == doc_comment_line) { + doc_comment_line--; + } +#endif // TOOLS_ENABLED + } + +#ifdef TOOLS_ENABLED + // Consume doc comments. + class_doc_line = MIN(class_doc_line, doc_comment_line - 1); + if (has_comment(doc_comment_line)) { + if constexpr (std::is_same_v<T, ClassNode>) { + get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true); + } else { + member->doc_description = get_doc_comment(doc_comment_line); + } } +#endif // TOOLS_ENABLED + if (member->identifier != nullptr) { // Enums may be unnamed. // TODO: Consider names in outer scope too, for constants and classes (and static functions?) @@ -743,6 +831,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper VariableNode *variable = alloc_node<VariableNode>(); variable->identifier = parse_identifier(); + variable->export_info.name = variable->identifier->name; if (match(GDScriptTokenizer::Token::COLON)) { if (check(GDScriptTokenizer::Token::NEWLINE)) { @@ -776,6 +865,9 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper if (match(GDScriptTokenizer::Token::EQUAL)) { // Initializer. variable->initializer = parse_expression(false); + if (variable->initializer == nullptr) { + push_error(R"(Expected expression for variable initial value after "=".)"); + } variable->assignments++; } @@ -789,8 +881,6 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper end_statement("variable declaration"); - variable->export_info.name = variable->identifier->name; - return variable; } @@ -943,6 +1033,8 @@ GDScriptParser::ConstantNode *GDScriptParser::parse_constant() { push_error(R"(Expected initializer expression for constant.)"); return nullptr; } + } else { + return nullptr; } end_statement("constant declaration"); @@ -1035,12 +1127,13 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { break; // Allow trailing comma. } - if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) { + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) { EnumNode::Value item; item.identifier = parse_identifier(); 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); @@ -1079,41 +1172,37 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { pop_multiline(); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); - end_statement("enum"); - - return enum_node; -} - -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; +#ifdef TOOLS_ENABLED + // Enum values documentaion. + for (int i = 0; i < enum_node->values.size(); i++) { + if (i == enum_node->values.size() - 1) { + // If close bracket is same line as last value. + if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) { + if (named) { + enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); + } else { + current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); + } + } + } else { + // If two values are same line. + if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) { + if (named) { + enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true); + } else { + current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true)); + } + } } - _static = true; } +#endif // TOOLS_ENABLED - FunctionNode *function = alloc_node<FunctionNode>(); - make_completion_context(COMPLETION_OVERRIDE_METHOD, function); - - if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { - return nullptr; - } - - FunctionNode *previous_function = current_function; - current_function = function; - - function->identifier = parse_identifier(); - function->is_static = _static; - - push_multiline(true); - consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)"); + end_statement("enum"); - SuiteNode *body = alloc_node<SuiteNode>(); - SuiteNode *previous_suite = current_suite; - current_suite = body; + return enum_node; +} +void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type) { if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { bool default_used = false; do { @@ -1133,29 +1222,61 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { 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)); + if (p_function->parameters_indices.has(parameter->identifier->name)) { + push_error(vformat(R"(Parameter with name "%s" was already declared for this %s.)", parameter->identifier->name, p_type)); } else { - function->parameters_indices[parameter->identifier->name] = function->parameters.size(); - function->parameters.push_back(parameter); - body->add_local(parameter); + p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size(); + p_function->parameters.push_back(parameter); + p_body->add_local(parameter, current_function); } } while (match(GDScriptTokenizer::Token::COMMA)); } pop_multiline(); - consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*"); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, vformat(R"*(Expected closing ")" after %s parameters.)*", p_type)); 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) { + make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, p_function); + p_function->return_type = parse_type(true); + if (p_function->return_type == nullptr) { push_error(R"(Expected return type or "void" after "->".)"); } } // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after function declaration.)"); + consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type)); +} + +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; + } + + FunctionNode *function = alloc_node<FunctionNode>(); + make_completion_context(COMPLETION_OVERRIDE_METHOD, function); + + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { + return nullptr; + } + + FunctionNode *previous_function = current_function; + current_function = function; + + function->identifier = parse_identifier(); + function->is_static = _static; + + SuiteNode *body = alloc_node<SuiteNode>(); + SuiteNode *previous_suite = current_suite; + current_suite = body; + + push_multiline(true); + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)"); + parse_function_signature(function, body, "function"); current_suite = previous_suite; function->body = parse_suite("function declaration", body); @@ -1241,29 +1362,34 @@ bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_ta return true; } -GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite) { +GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite, bool p_for_lambda) { SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>(); suite->parent_block = current_suite; + suite->parent_function = current_function; current_suite = suite; bool multiline = false; - if (check(GDScriptTokenizer::Token::NEWLINE)) { + if (match(GDScriptTokenizer::Token::NEWLINE)) { multiline = true; } if (multiline) { - consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after %s.)", p_context)); - if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) { current_suite = suite->parent_block; return suite; } } + int error_count = 0; + do { Node *statement = parse_statement(); if (statement == nullptr) { + if (error_count++ > 100) { + push_error("Too many statement errors.", suite); + break; + } continue; } suite->statements.push_back(statement); @@ -1276,7 +1402,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, 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)); } - current_suite->add_local(variable); + current_suite->add_local(variable, current_function); break; } case Node::CONSTANT: { @@ -1291,19 +1417,29 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, } push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name)); } - current_suite->add_local(constant); + current_suite->add_local(constant, current_function); break; } default: break; } - } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()); + } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); if (multiline) { - consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context)); + if (!lambda_ended) { + consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context)); + + } else { + match(GDScriptTokenizer::Token::DEDENT); + } + } else if (previous.type == GDScriptTokenizer::Token::SEMICOLON) { + consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after ";" at the end of %s.)", p_context)); } + if (p_for_lambda) { + lambda_ended = true; + } current_suite = suite->parent_block; return suite; } @@ -1356,10 +1492,14 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { advance(); ReturnNode *n_return = alloc_node<ReturnNode>(); if (!is_statement_end()) { - if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { + if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { push_error(R"(Constructor cannot return a value.)"); } n_return->return_value = parse_expression(false); + } else if (in_lambda && !is_statement_end_token()) { + // Try to parse it anyway as this might not be the statement end in a lambda. + // If this fails the expression will be nullptr, but that's the same as no return, so it's fine. + n_return->return_value = parse_expression(false); } result = n_return; @@ -1388,10 +1528,18 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { default: { // Expression statement. ExpressionNode *expression = parse_expression(true); // Allow assignment here. + bool has_ended_lambda = false; if (expression == nullptr) { - push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name())); + if (in_lambda) { + // If it's not a valid expression beginning, it might be the continuation of the outer expression where this lambda is. + lambda_ended = true; + has_ended_lambda = true; + } else { + push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name())); + } } end_statement("expression"); + lambda_ended = lambda_ended || has_ended_lambda; result = expression; #ifdef DEBUG_ENABLED @@ -1412,9 +1560,13 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { } #ifdef DEBUG_ENABLED - if (unreachable) { + if (unreachable && result != nullptr) { current_suite->has_unreachable_code = true; - push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name); + if (current_function) { + push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier ? current_function->identifier->name : "<anonymous lambda>"); + } else { + // TODO: Properties setters and getters with unreachable code are not being warned + } } #endif @@ -1438,12 +1590,9 @@ GDScriptParser::AssertNode *GDScriptParser::parse_assert() { if (match(GDScriptTokenizer::Token::COMMA)) { // Error message. - if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected error message for assert after ",".)")) { - assert->message = parse_literal(); - if (assert->message->value.get_type() != Variant::STRING) { - push_error(R"(Expected string for assert error message.)"); - } - } else { + assert->message = parse_expression(false); + if (assert->message == nullptr) { + push_error(R"(Expected error message for assert after ",".)"); return nullptr; } } @@ -1469,7 +1618,9 @@ GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { } current_suite->has_continue = true; end_statement(R"("continue")"); - return alloc_node<ContinueNode>(); + ContinueNode *cont = alloc_node<ContinueNode>(); + cont->is_for_match = is_continue_match; + return cont; } GDScriptParser::ForNode *GDScriptParser::parse_for() { @@ -1488,14 +1639,16 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; + bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; + is_continue_match = false; SuiteNode *suite = alloc_node<SuiteNode>(); if (n_for->variable) { - suite->add_local(SuiteNode::Local(n_for->variable)); + suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } suite->parent_for = n_for; @@ -1504,6 +1657,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { // Reset break/continue state. can_break = could_break; can_continue = could_continue; + is_continue_match = was_continue_match; return n_for; } @@ -1630,7 +1784,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { branch->patterns.push_back(pattern); } while (match(GDScriptTokenizer::Token::COMMA)); - if (branch->patterns.empty()) { + if (branch->patterns.is_empty()) { push_error(R"(No pattern found for "match" branch.)"); } @@ -1638,8 +1792,10 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { // Save continue state. bool could_continue = can_continue; + bool was_continue_match = is_continue_match; // Allow continue for match. can_continue = true; + is_continue_match = true; SuiteNode *suite = alloc_node<SuiteNode>(); if (branch->patterns.size() > 0) { @@ -1647,7 +1803,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { branch->patterns[0]->binds.get_key_list(&binds); for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) { - SuiteNode::Local local(branch->patterns[0]->binds[E->get()]); + SuiteNode::Local local(branch->patterns[0]->binds[E->get()], current_function); suite->add_local(local); } } @@ -1656,6 +1812,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { // Restore continue state. can_continue = could_continue; + is_continue_match = was_continue_match; return branch; } @@ -1813,16 +1970,19 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; + bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; + is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); // Reset break/continue state. can_break = could_break; can_continue = could_continue; + is_continue_match = was_continue_match; return n_while; } @@ -1843,7 +2003,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr // Completion can appear whenever an expression is expected. make_completion_context(COMPLETION_IDENTIFIER, nullptr); - GDScriptTokenizer::Token token = advance(); + GDScriptTokenizer::Token token = current; ParseFunction prefix_rule = get_rule(token.type)->prefix; if (prefix_rule == nullptr) { @@ -1851,6 +2011,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr return nullptr; } + advance(); // Only consume the token if there's a valid rule. + ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); while (p_precedence <= get_rule(current.type)->precedence) { @@ -1892,6 +2054,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode if (current_suite != nullptr && current_suite->has_local(identifier->name)) { const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); + + identifier->source_function = declaration.source_function; switch (declaration.type) { case SuiteNode::Local::CONSTANT: identifier->source = IdentifierNode::LOCAL_CONSTANT; @@ -1942,8 +2106,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ } GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) { - if (!current_function || current_function->is_static) { - push_error(R"(Cannot use "self" outside a non-static function.)"); + if (current_function && current_function->is_static) { + push_error(R"(Cannot use "self" inside a static function.)"); + } + if (in_lambda) { + push_error(R"(Cannot use "self" inside a lambda.)"); } SelfNode *self = alloc_node<SelfNode>(); self->current_class = current_class; @@ -2007,6 +2174,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN return operation; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { + // check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN + consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "not" in content-test operator.)"); + ExpressionNode *in_operation = parse_binary_operator(p_previous_operand, p_can_assign); + UnaryOpNode *operation = alloc_node<UnaryOpNode>(); + operation->operation = UnaryOpNode::OP_LOGIC_NOT; + operation->variant_op = Variant::OP_NOT; + operation->operand = in_operation; + return operation; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { GDScriptTokenizer::Token op = previous; BinaryOpNode *operation = alloc_node<BinaryOpNode>(); @@ -2319,6 +2497,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } + key->is_constant = true; + key->reduced_value = static_cast<IdentifierNode *>(key)->name; break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -2365,7 +2545,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * if (for_completion) { bool is_builtin = false; - if (p_previous_operand->type == Node::IDENTIFIER) { + if (p_previous_operand && 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) { @@ -2468,26 +2648,28 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre } } - if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { - // Arguments. - push_completion_call(call); - make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0, true); - int argument_index = 0; - do { - make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument_index++, true); - if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { - // Allow for trailing comma. - break; - } - ExpressionNode *argument = parse_expression(false); - if (argument == nullptr) { - push_error(R"(Expected expression as the function argument.)"); - } else { - call->arguments.push_back(argument); - } - } while (match(GDScriptTokenizer::Token::COMMA)); - pop_completion_call(); + // Arguments. + CompletionType ct = COMPLETION_CALL_ARGUMENTS; + if (call->function_name == "load") { + ct = COMPLETION_RESOURCE_PATH; } + push_completion_call(call); + int argument_index = 0; + do { + make_completion_context(ct, call, argument_index++, true); + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; + } + ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { + push_error(R"(Expected expression as the function argument.)"); + } else { + call->arguments.push_back(argument); + } + ct = COMPLETION_CALL_ARGUMENTS; + } while (match(GDScriptTokenizer::Token::COMMA)); + pop_completion_call(); pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*"); @@ -2498,7 +2680,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { if (match(GDScriptTokenizer::Token::LITERAL)) { if (previous.literal.get_type() != Variant::STRING) { - push_error(R"(Expect node path as string or identifer after "$".)"); + push_error(R"(Expect node path as string or identifier after "$".)"); return nullptr; } GetNodeNode *get_node = alloc_node<GetNodeNode>(); @@ -2521,7 +2703,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p } while (match(GDScriptTokenizer::Token::SLASH)); return get_node; } else { - push_error(R"(Expect node path as string or identifer after "$".)"); + push_error(R"(Expect node path as string or identifier after "$".)"); return nullptr; } } @@ -2550,6 +2732,65 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ return preload; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) { + LambdaNode *lambda = alloc_node<LambdaNode>(); + lambda->parent_function = current_function; + FunctionNode *function = alloc_node<FunctionNode>(); + function->source_lambda = lambda; + + function->is_static = current_function != nullptr ? current_function->is_static : false; + + if (match(GDScriptTokenizer::Token::IDENTIFIER)) { + function->identifier = parse_identifier(); + } + + bool multiline_context = multiline_stack.back()->get(); + + // Reset the multiline stack since we don't want the multiline mode one in the lambda body. + push_multiline(false); + if (multiline_context) { + tokenizer.push_expression_indented_block(); + } + + push_multiline(true); // For the parameters. + if (function->identifier) { + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after lambda name.)"); + } else { + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after "func".)"); + } + + FunctionNode *previous_function = current_function; + current_function = function; + + SuiteNode *body = alloc_node<SuiteNode>(); + SuiteNode *previous_suite = current_suite; + current_suite = body; + + parse_function_signature(function, body, "lambda"); + + current_suite = previous_suite; + + bool previous_in_lambda = in_lambda; + in_lambda = true; + + function->body = parse_suite("lambda declaration", body, true); + + pop_multiline(); + + if (multiline_context) { + // If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens. + while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) { + current = tokenizer.scan(); // Not advance() since we don't want to change the previous token. + } + tokenizer.pop_expression_indented_block(); + } + + current_function = previous_function; + in_lambda = previous_in_lambda; + lambda->function = function; + return lambda; +} + 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; @@ -2586,6 +2827,19 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { type->type_chain.push_back(type_element); + if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { + // Typed collection (like Array[int]). + type->container_type = parse_type(false); // Don't allow void for array element type. + if (type->container_type == nullptr) { + push_error(R"(Expected type for collection after "[".)"); + type = nullptr; + } else if (type->container_type->container_type != nullptr) { + push_error("Nested typed collections are not supported."); + } + consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)"); + return type; + } + int chain_index = 1; while (match(GDScriptTokenizer::Token::PERIOD)) { make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++); @@ -2598,6 +2852,218 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { return type; } +#ifdef TOOLS_ENABLED +static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) { + int start_block = p_line.rfind("[codeblock]"); + int end_block = p_line.rfind("[/codeblock]"); + + if (start_block != -1 && r_block_begins) { + *r_block_begins = start_block; + } + + if (p_already_in) { + if (end_block == -1) { + return true; + } else if (start_block == -1) { + return false; + } else { + return start_block > end_block; + } + } else { + if (start_block == -1) { + return false; + } else if (end_block == -1) { + return true; + } else { + return start_block > end_block; + } + } +} + +bool GDScriptParser::has_comment(int p_line) { + return tokenizer.get_comments().has(p_line); +} + +String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { + const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + ERR_FAIL_COND_V(!comments.has(p_line), String()); + + if (p_single_line) { + if (comments[p_line].comment.begins_with("##")) { + return comments[p_line].comment.trim_prefix("##").strip_edges(); + } + return ""; + } + + String doc; + + int line = p_line; + bool in_codeblock = false; + + while (comments.has(line - 1)) { + if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { + break; + } + line--; + } + + int codeblock_begins = 0; + while (comments.has(line)) { + if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { + break; + } + String doc_line = comments[line].comment.trim_prefix("##"); + + in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); + + if (in_codeblock) { + int i = 0; + for (; i < codeblock_begins; i++) { + if (doc_line[i] != ' ') { + break; + } + } + doc_line = doc_line.substr(i); + } else { + doc_line = doc_line.strip_edges(); + } + String line_join = (in_codeblock) ? "\n" : " "; + + doc = (doc.is_empty()) ? doc_line : doc + line_join + doc_line; + line++; + } + + return doc; +} + +void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) { + const Map<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + if (!comments.has(p_line)) { + return; + } + ERR_FAIL_COND(p_brief != "" || p_desc != "" || p_tutorials.size() != 0); + + int line = p_line; + bool in_codeblock = false; + enum Mode { + BRIEF, + DESC, + TUTORIALS, + DONE, + }; + Mode mode = BRIEF; + + if (p_inner_class) { + while (comments.has(line - 1)) { + if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { + break; + } + line--; + } + } + + int codeblock_begins = 0; + while (comments.has(line)) { + if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { + break; + } + + String title, link; // For tutorials. + String doc_line = comments[line++].comment.trim_prefix("##"); + String striped_line = doc_line.strip_edges(); + + // Set the read mode. + if (striped_line.begins_with("@desc:") && p_desc == "") { + mode = DESC; + striped_line = striped_line.trim_prefix("@desc:"); + in_codeblock = _in_codeblock(doc_line, in_codeblock); + + } else if (striped_line.begins_with("@tutorial")) { + int begin_scan = String("@tutorial").length(); + if (begin_scan >= striped_line.length()) { + continue; // invalid syntax. + } + + if (striped_line[begin_scan] == ':') { // No title. + // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. + title = ""; + link = striped_line.trim_prefix("@tutorial:").strip_edges(); + + } else { + /* Syntax: + @tutorial ( The Title Here ) : http://the.url/ + ^ open ^ close ^ colon ^ url + */ + int open_bracket_pos = begin_scan, close_bracket_pos = 0; + while (open_bracket_pos < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) { + open_bracket_pos++; + } + if (open_bracket_pos == striped_line.length() || striped_line[open_bracket_pos++] != '(') { + continue; // invalid syntax. + } + close_bracket_pos = open_bracket_pos; + while (close_bracket_pos < striped_line.length() && striped_line[close_bracket_pos] != ')') { + close_bracket_pos++; + } + if (close_bracket_pos == striped_line.length()) { + continue; // invalid syntax. + } + + int colon_pos = close_bracket_pos + 1; + while (colon_pos < striped_line.length() && (striped_line[colon_pos] == ' ' || striped_line[colon_pos] == '\t')) { + colon_pos++; + } + if (colon_pos == striped_line.length() || striped_line[colon_pos++] != ':') { + continue; // invalid syntax. + } + + title = striped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); + link = striped_line.substr(colon_pos).strip_edges(); + } + + mode = TUTORIALS; + in_codeblock = false; + } else if (striped_line.is_empty()) { + continue; + } else { + // Tutorial docs are single line, we need a @tag after it. + if (mode == TUTORIALS) { + mode = DONE; + } + + in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); + } + + if (in_codeblock) { + int i = 0; + for (; i < codeblock_begins; i++) { + if (doc_line[i] != ' ') { + break; + } + } + doc_line = doc_line.substr(i); + } else { + doc_line = striped_line; + } + String line_join = (in_codeblock) ? "\n" : " "; + + switch (mode) { + case BRIEF: + p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line; + break; + case DESC: + p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line; + break; + case TUTORIALS: + p_tutorials.append(Pair<String, String>(title, link)); + break; + case DONE: + return; + } + } +} +#endif // TOOLS_ENABLED + GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Type p_token_type) { // Function table for expression parsing. // clang-format destroys the alignment here, so turn off for the table. @@ -2619,7 +3085,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty // 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, + { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_not_in_operator, PREC_CONTENT_TEST }, // 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, @@ -2631,8 +3097,8 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // LESS_LESS, { nullptr, &GDScriptParser::parse_binary_operator, PREC_BIT_SHIFT }, // GREATER_GREATER, // Math - { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_ADDITION }, // PLUS, - { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_operator, PREC_SUBTRACTION }, // MINUS, + { &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, @@ -2669,7 +3135,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // CONST, { nullptr, nullptr, PREC_NONE }, // ENUM, { nullptr, nullptr, PREC_NONE }, // EXTENDS, - { nullptr, nullptr, PREC_NONE }, // FUNC, + { &GDScriptParser::parse_lambda, 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, @@ -2718,7 +3184,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty // 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. + // Let's assume this is never invalid, since nothing generates a TK_MAX. return &rules[p_token_type]; } @@ -2784,7 +3250,9 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) Callable::CallError error; Vector<Variant> args = varray(string->name); const Variant *name = args.ptr(); - p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(name), 1, error)); + Variant r; + Variant::construct(parameter.type, r, &(name), 1, error); + p_annotation->resolved_arguments.push_back(r); if (error.error != Callable::CallError::CALL_OK) { push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); @@ -2806,7 +3274,9 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) } Callable::CallError error; const Variant *args = &value; - p_annotation->resolved_arguments.push_back(Variant::construct(parameter.type, &(args), 1, error)); + Variant r; + Variant::construct(parameter.type, r, &(args), 1, error); + p_annotation->resolved_arguments.push_back(r); if (error.error != Callable::CallError::CALL_OK) { push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); @@ -2856,24 +3326,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } variable->exported = true; - // TODO: Improving setting type, especially for range hints, which can be int or float. + variable->export_info.type = t_type; variable->export_info.hint = t_hint; - if (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; - } - 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; - } - 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. - } - String hint_string; for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { if (i > 0) { @@ -2884,6 +3340,86 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = hint_string; + // This is called after tne analyzer is done finding the type, so this should be set here. + DataType export_type = variable->get_datatype(); + + if (p_annotation->name == "@export") { + if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { + push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); + return false; + } + + bool is_array = false; + + if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { + export_type = export_type.get_container_element_type(); // Use inner type for. + is_array = true; + } + + if (export_type.is_variant() || export_type.has_no_type()) { + push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); + return false; + } + + switch (export_type.kind) { + case GDScriptParser::DataType::BUILTIN: + variable->export_info.type = export_type.builtin_type; + variable->export_info.hint = PROPERTY_HINT_NONE; + variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); + break; + case GDScriptParser::DataType::NATIVE: + if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = get_real_class_name(export_type.native_type); + } else { + push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + return false; + } + break; + case GDScriptParser::DataType::ENUM: { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) { + enum_hint_string += E->key().operator String().camelcase_to_underscore(true).capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E->get()).xml_escape(); + + if (E->next()) { + enum_hint_string += ","; + } + } + + variable->export_info.hint_string = enum_hint_string; + } break; + default: + // TODO: Allow custom user resources. + push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + break; + } + + if (is_array) { + String hint_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + hint_prefix += "/" + itos(variable->export_info.hint); + } + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; + variable->export_info.type = Variant::ARRAY; + } + } else { + // Validate variable type with export. + if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) { + // Allow float/int conversion. + if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) { + push_error(vformat(R"("%s" annotation requires a variable of type "%s" but type "%s" was given instead.)", p_annotation->name.operator String(), Variant::get_type_name(t_type), export_type.to_string()), variable); + return false; + } + } + } + return true; } @@ -2969,6 +3505,9 @@ String GDScriptParser::DataType::to_string() const { if (builtin_type == Variant::NIL) { return "null"; } + if (builtin_type == Variant::ARRAY && has_container_element_type()) { + return vformat("Array[%s]", container_element_type->to_string()); + } return Variant::get_type_name(builtin_type); case NATIVE: if (is_meta_type) { @@ -2988,11 +3527,11 @@ String GDScriptParser::DataType::to_string() const { return script_type->get_class_name().operator String(); } String name = script_type->get_name(); - if (!name.empty()) { + if (!name.is_empty()) { return name; } name = script_path; - if (!name.empty()) { + if (!name.is_empty()) { return name; } return native_type.operator String(); @@ -3037,7 +3576,7 @@ void GDScriptParser::TreePrinter::decrease_indent() { } void GDScriptParser::TreePrinter::push_line(const String &p_line) { - if (!p_line.empty()) { + if (!p_line.is_empty()) { push_text(p_line); } printed += "\n"; @@ -3246,7 +3785,7 @@ void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) { if (p_class->extends_used) { bool first = true; push_text(" Extends "); - if (!p_class->extends_path.empty()) { + if (!p_class->extends_path.is_empty()) { push_text(vformat(R"("%s")", p_class->extends_path)); first = false; } @@ -3332,6 +3871,10 @@ void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary) } void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) { + if (p_expression == nullptr) { + push_text("<invalid expression>"); + return; + } switch (p_expression->type) { case Node::ARRAY: print_array(static_cast<ArrayNode *>(p_expression)); @@ -3360,6 +3903,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) case Node::IDENTIFIER: print_identifier(static_cast<IdentifierNode *>(p_expression)); break; + case Node::LAMBDA: + print_lambda(static_cast<LambdaNode *>(p_expression)); + break; case Node::LITERAL: print_literal(static_cast<LiteralNode *>(p_expression)); break; @@ -3419,12 +3965,17 @@ void GDScriptParser::TreePrinter::print_for(ForNode *p_for) { decrease_indent(); } -void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function) { +void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context) { 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(p_context); + push_text(" "); + if (p_function->identifier) { + print_identifier(p_function->identifier); + } else { + push_text("<anonymous>"); + } push_text("( "); for (int i = 0; i < p_function->parameters.size(); i++) { if (i > 0) { @@ -3478,6 +4029,18 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { } } +void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) { + print_function(p_lambda->function, "Lambda"); + push_text("| captures [ "); + for (int i = 0; i < p_lambda->captures.size(); i++) { + if (i > 0) { + push_text(" , "); + } + push_text(p_lambda->captures[i]->name.operator String()); + } + push_line(" ]"); +} + void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) { // Prefix for string types. switch (p_literal->value.get_type()) { @@ -3708,7 +4271,7 @@ void GDScriptParser::TreePrinter::print_ternary_op(TernaryOpNode *p_ternary_op) } void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) { - if (p_type->type_chain.empty()) { + if (p_type->type_chain.is_empty()) { push_text("Void"); } else { for (int i = 0; i < p_type->type_chain.size(); i++) { @@ -3828,7 +4391,7 @@ void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { if (p_parser.is_tool()) { push_line("@tool"); } - if (!p_parser.get_tree()->icon_path.empty()) { + if (!p_parser.get_tree()->icon_path.is_empty()) { push_text(R"(@icon (")"); push_text(p_parser.get_tree()->icon_path); push_line("\")"); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 7d8ae7fc55..b1b29a7bd1 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,23 +31,22 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H -#include "core/hash_map.h" #include "core/io/multiplayer_api.h" -#include "core/list.h" -#include "core/map.h" -#include "core/reference.h" -#include "core/resource.h" -#include "core/script_language.h" -#include "core/string_name.h" -#include "core/ustring.h" -#include "core/variant.h" -#include "core/vector.h" +#include "core/io/resource.h" +#include "core/object/reference.h" +#include "core/object/script_language.h" +#include "core/string/string_name.h" +#include "core/string/ustring.h" +#include "core/templates/hash_map.h" +#include "core/templates/list.h" +#include "core/templates/map.h" +#include "core/templates/vector.h" +#include "core/variant/variant.h" #include "gdscript_cache.h" -#include "gdscript_functions.h" #include "gdscript_tokenizer.h" #ifdef DEBUG_ENABLED -#include "core/string_builder.h" +#include "core/string/string_builder.h" #include "gdscript_warning.h" #endif // DEBUG_ENABLED @@ -77,6 +76,7 @@ public: struct GetNodeNode; struct IdentifierNode; struct IfNode; + struct LambdaNode; struct LiteralNode; struct MatchNode; struct MatchBranchNode; @@ -95,7 +95,12 @@ public: struct VariableNode; struct WhileNode; - struct DataType { + class DataType { + private: + // Private access so we can control memory management. + DataType *container_element_type = nullptr; + + public: enum Kind { BUILTIN, NATIVE, @@ -105,7 +110,6 @@ public: ENUM_VALUE, // Value from enumeration. VARIANT, // Can be any type. UNRESOLVED, - // TODO: Enum }; Kind kind = UNRESOLVED; @@ -129,7 +133,7 @@ public: ClassNode *class_type = nullptr; MethodInfo method_info; // For callable/signals. - HashMap<StringName, int> enum_values; // For enums. + Map<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; } @@ -137,6 +141,26 @@ public: _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; + _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) { + container_element_type = memnew(DataType(p_type)); + } + + _FORCE_INLINE_ DataType get_container_element_type() const { + ERR_FAIL_COND_V(container_element_type == nullptr, DataType()); + return *container_element_type; + } + + _FORCE_INLINE_ bool has_container_element_type() const { + return container_element_type != nullptr; + } + + _FORCE_INLINE_ void unset_container_element_type() { + if (container_element_type) { + memdelete(container_element_type); + }; + container_element_type = nullptr; + } + 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. @@ -174,6 +198,37 @@ public: bool operator!=(const DataType &p_other) const { return !(this->operator==(p_other)); } + + DataType &operator=(const DataType &p_other) { + kind = p_other.kind; + type_source = p_other.type_source; + is_constant = p_other.is_constant; + is_meta_type = p_other.is_meta_type; + is_coroutine = p_other.is_coroutine; + builtin_type = p_other.builtin_type; + native_type = p_other.native_type; + enum_type = p_other.enum_type; + script_type = p_other.script_type; + script_path = p_other.script_path; + class_type = p_other.class_type; + method_info = p_other.method_info; + enum_values = p_other.enum_values; + unset_container_element_type(); + if (p_other.has_container_element_type()) { + set_container_element_type(p_other.get_container_element_type()); + } + return *this; + } + + DataType() = default; + + DataType(const DataType &p_other) { + *this = p_other; + } + + ~DataType() { + unset_container_element_type(); + } }; struct ParserError { @@ -213,6 +268,7 @@ public: GET_NODE, IDENTIFIER, IF, + LAMBDA, LITERAL, MATCH, MATCH_BRANCH, @@ -287,7 +343,7 @@ public: struct AssertNode : public Node { ExpressionNode *condition = nullptr; - LiteralNode *message = nullptr; + ExpressionNode *message = nullptr; AssertNode() { type = ASSERT; @@ -352,7 +408,7 @@ public: OP_COMP_GREATER_EQUAL, }; - OpType operation; + OpType operation = OpType::OP_ADDITION; Variant::Operator variant_op = Variant::OP_MAX; ExpressionNode *left_operand = nullptr; ExpressionNode *right_operand = nullptr; @@ -413,9 +469,16 @@ public: int line = 0; int leftmost_column = 0; int rightmost_column = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED }; + IdentifierNode *identifier = nullptr; Vector<Value> values; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED EnumNode() { type = ENUM; @@ -568,6 +631,17 @@ public: Vector<StringName> extends; // List for indexing: extends A.B.C DataType base_type; String fqcn; // Fully-qualified class name. Identifies uniquely any class in the project. +#ifdef TOOLS_ENABLED + String doc_description; + String doc_brief_description; + Vector<Pair<String, String>> doc_tutorials; + + // EnumValue docs are parsed after itself, so we need a method to add/modify the doc property later. + void set_enum_value_doc(const StringName &p_name, const String &p_doc_description) { + ERR_FAIL_INDEX(members_indices[p_name], members.size()); + members.write[members_indices[p_name]].enum_value.doc_description = p_doc_description; + } +#endif // TOOLS_ENABLED bool resolved_interface = false; bool resolved_body = false; @@ -602,6 +676,9 @@ public: TypeNode *datatype_specifier = nullptr; bool infer_datatype = false; int usages = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED ConstantNode() { type = CONSTANT; @@ -609,6 +686,7 @@ public: }; struct ContinueNode : public Node { + bool is_for_match = false; ContinueNode() { type = CONTINUE; } @@ -652,6 +730,11 @@ public: bool is_coroutine = false; MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; MethodInfo info; + LambdaNode *source_lambda = nullptr; +#ifdef TOOLS_ENABLED + Vector<Variant> default_arg_values; + String doc_description; +#endif // TOOLS_ENABLED bool resolved_signature = false; bool resolved_body = false; @@ -691,6 +774,7 @@ public: VariableNode *variable_source; IdentifierNode *bind_source; }; + FunctionNode *source_function = nullptr; int usages = 0; // Useful for binds/iterator variable. @@ -709,6 +793,21 @@ public: } }; + struct LambdaNode : public ExpressionNode { + FunctionNode *function = nullptr; + FunctionNode *parent_function = nullptr; + Vector<IdentifierNode *> captures; + Map<StringName, int> captures_indices; + + bool has_name() const { + return function && function->identifier; + } + + LambdaNode() { + type = LAMBDA; + } + }; + struct LiteralNode : public ExpressionNode { Variant value; @@ -728,7 +827,7 @@ public: struct MatchBranchNode : public Node { Vector<PatternNode *> patterns; - SuiteNode *block; + SuiteNode *block = nullptr; bool has_wildcard = false; MatchBranchNode() { @@ -819,6 +918,9 @@ public: IdentifierNode *identifier = nullptr; Vector<ParameterNode *> parameters; HashMap<StringName, int> parameters_indices; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED SignalNode() { type = SIGNAL; @@ -859,6 +961,7 @@ public: IdentifierNode *bind; }; StringName name; + FunctionNode *source_function = nullptr; int start_line = 0, end_line = 0; int start_column = 0, end_column = 0; @@ -868,10 +971,11 @@ public: String get_name() const; Local() {} - Local(ConstantNode *p_constant) { + Local(ConstantNode *p_constant, FunctionNode *p_source_function) { type = CONSTANT; constant = p_constant; name = p_constant->identifier->name; + source_function = p_source_function; start_line = p_constant->start_line; end_line = p_constant->end_line; @@ -880,10 +984,11 @@ public: leftmost_column = p_constant->leftmost_column; rightmost_column = p_constant->rightmost_column; } - Local(VariableNode *p_variable) { + Local(VariableNode *p_variable, FunctionNode *p_source_function) { type = VARIABLE; variable = p_variable; name = p_variable->identifier->name; + source_function = p_source_function; start_line = p_variable->start_line; end_line = p_variable->end_line; @@ -892,10 +997,11 @@ public: leftmost_column = p_variable->leftmost_column; rightmost_column = p_variable->rightmost_column; } - Local(ParameterNode *p_parameter) { + Local(ParameterNode *p_parameter, FunctionNode *p_source_function) { type = PARAMETER; parameter = p_parameter; name = p_parameter->identifier->name; + source_function = p_source_function; start_line = p_parameter->start_line; end_line = p_parameter->end_line; @@ -904,10 +1010,11 @@ public: leftmost_column = p_parameter->leftmost_column; rightmost_column = p_parameter->rightmost_column; } - Local(IdentifierNode *p_identifier) { + Local(IdentifierNode *p_identifier, FunctionNode *p_source_function) { type = FOR_VARIABLE; bind = p_identifier; name = p_identifier->name; + source_function = p_source_function; start_line = p_identifier->start_line; end_line = p_identifier->end_line; @@ -932,9 +1039,9 @@ public: 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) { + void add_local(T *p_local, FunctionNode *p_source_function) { locals_indices[p_local->identifier->name] = locals.size(); - locals.push_back(Local(p_local)); + locals.push_back(Local(p_local, p_source_function)); } void add_local(const Local &p_local) { locals_indices[p_local.name] = locals.size(); @@ -959,6 +1066,7 @@ public: struct TypeNode : public Node { Vector<IdentifierNode *> type_chain; + TypeNode *container_type = nullptr; TypeNode() { type = TYPE; @@ -973,7 +1081,7 @@ public: OP_LOGIC_NOT, }; - OpType operation; + OpType operation = OP_POSITIVE; Variant::Operator variant_op = Variant::OP_MAX; ExpressionNode *operand = nullptr; @@ -1011,6 +1119,9 @@ public: MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; int assignments = 0; int usages = 0; +#ifdef TOOLS_ENABLED + String doc_description; +#endif // TOOLS_ENABLED VariableNode() { type = VARIABLE; @@ -1079,6 +1190,7 @@ private: bool panic_mode = false; bool can_break = false; bool can_continue = false; + bool is_continue_match = false; // Whether a `continue` will act on a `match`. bool is_ignoring_warnings = false; List<bool> multiline_stack; @@ -1103,6 +1215,8 @@ private: CompletionCall completion_call; List<CompletionCall> completion_call_stack; bool passed_cursor = false; + bool in_lambda = false; + bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed. typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target); struct AnnotationInfo { @@ -1140,8 +1254,7 @@ private: PREC_BIT_XOR, PREC_BIT_AND, PREC_BIT_SHIFT, - PREC_SUBTRACTION, - PREC_ADDITION, + PREC_ADDITION_SUBTRACTION, PREC_FACTOR, PREC_SIGN, PREC_BIT_NOT, @@ -1191,10 +1304,11 @@ private: GDScriptTokenizer::Token advance(); bool match(GDScriptTokenizer::Token::Type p_token_type); - bool check(GDScriptTokenizer::Token::Type p_token_type); + bool check(GDScriptTokenizer::Token::Type p_token_type) const; bool consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message); - bool is_at_end(); - bool is_statement_end(); + bool is_at_end() const; + bool is_statement_end_token() const; + bool is_statement_end() const; void end_statement(const String &p_context); void synchronize(); void push_multiline(bool p_state); @@ -1212,7 +1326,8 @@ private: EnumNode *parse_enum(); ParameterNode *parse_parameter(); FunctionNode *parse_function(); - SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr); + void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type); + SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false); // 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); @@ -1254,6 +1369,7 @@ private: 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_binary_not_in_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); @@ -1266,15 +1382,23 @@ private: 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_lambda(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); - static GDScriptFunctions::Function get_builtin_function(const StringName &p_name); + static StringName get_real_class_name(const StringName &p_source); CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } @@ -1320,10 +1444,11 @@ public: 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_function(FunctionNode *p_function, const String &p_context = "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_lambda(LambdaNode *p_lambda); void print_literal(LiteralNode *p_literal); void print_match(MatchNode *p_match); void print_match_branch(MatchBranchNode *p_match_branch); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 737e920693..3f14156dfa 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,7 +30,7 @@ #include "gdscript_tokenizer.h" -#include "core/error_macros.h" +#include "core/error/error_macros.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" @@ -221,8 +221,8 @@ String GDScriptTokenizer::get_token_name(Token::Type p_token_type) { void GDScriptTokenizer::set_source_code(const String &p_source_code) { source = p_source_code; - if (source.empty()) { - _source = L""; + if (source.is_empty()) { + _source = U""; } else { _source = source.ptr(); } @@ -242,6 +242,16 @@ void GDScriptTokenizer::set_multiline_mode(bool p_state) { multiline_mode = p_state; } +void GDScriptTokenizer::push_expression_indented_block() { + indent_stack_stack.push_back(indent_stack); +} + +void GDScriptTokenizer::pop_expression_indented_block() { + ERR_FAIL_COND(indent_stack_stack.size() == 0); + indent_stack = indent_stack_stack.back()->get(); + indent_stack_stack.pop_back(); +} + int GDScriptTokenizer::get_cursor_line() const { return cursor_line; } @@ -263,7 +273,7 @@ bool GDScriptTokenizer::is_past_cursor() const { return true; } -CharType GDScriptTokenizer::_advance() { +char32_t GDScriptTokenizer::_advance() { if (unlikely(_is_at_end())) { return '\0'; } @@ -282,15 +292,15 @@ CharType GDScriptTokenizer::_advance() { return _peek(-1); } -void GDScriptTokenizer::push_paren(CharType p_char) { +void GDScriptTokenizer::push_paren(char32_t p_char) { paren_stack.push_back(p_char); } -bool GDScriptTokenizer::pop_paren(CharType p_expected) { - if (paren_stack.empty()) { +bool GDScriptTokenizer::pop_paren(char32_t p_expected) { + if (paren_stack.is_empty()) { return false; } - CharType actual = paren_stack.back()->get(); + char32_t actual = paren_stack.back()->get(); paren_stack.pop_back(); return actual == p_expected; @@ -302,19 +312,19 @@ GDScriptTokenizer::Token GDScriptTokenizer::pop_error() { return error; } -static bool _is_alphanumeric(CharType c) { +static bool _is_alphanumeric(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } -static bool _is_digit(CharType c) { +static bool _is_digit(char32_t c) { return (c >= '0' && c <= '9'); } -static bool _is_hex_digit(CharType c) { +static bool _is_hex_digit(char32_t c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } -static bool _is_binary_digit(CharType c) { +static bool _is_binary_digit(char32_t c) { return (c == '0' || c == '1'); } @@ -404,8 +414,8 @@ void GDScriptTokenizer::push_error(const Token &p_error) { error_stack.push_back(p_error); } -GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) { - if (paren_stack.empty()) { +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())); @@ -413,8 +423,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) { return error; } -GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, Token::Type p_double_type) { - const CharType *next = _current + 1; +GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, Token::Type p_double_type) { + const char32_t *next = _current + 1; int chars = 2; // Two already matched. // Test before consuming characters, since we don't want to consume more than needed. @@ -578,7 +588,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { } void GDScriptTokenizer::newline(bool p_make_token) { - // Don't overwrite previous newline, nor create if we want a line contination. + // Don't overwrite previous newline, nor create if we want a line continuation. if (p_make_token && !pending_newline && !line_continuation) { Token newline(Token::NEWLINE); newline.start_line = line; @@ -602,7 +612,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { bool has_decimal = false; bool has_exponent = false; bool has_error = false; - bool (*digit_check_func)(CharType) = _is_digit; + bool (*digit_check_func)(char32_t) = _is_digit; if (_peek(-1) == '.') { has_decimal = true; @@ -621,7 +631,21 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { } // Allow '_' to be used in a number, for readability. + bool previous_was_underscore = false; while (digit_check_func(_peek()) || _peek() == '_') { + if (_peek() == '_') { + if (previous_was_underscore) { + Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = true; + } else { + previous_was_underscore = false; + } _advance(); } @@ -672,7 +696,29 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); } // Consume exponent digits. + if (!_is_digit(_peek())) { + Token error = make_error(R"(Expected exponent value after "e".)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = false; while (_is_digit(_peek()) || _peek() == '_') { + if (_peek() == '_') { + if (previous_was_underscore) { + Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = true; + } else { + previous_was_underscore = false; + } _advance(); } } @@ -730,7 +776,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); } - CharType quote_char = _peek(-1); + char32_t quote_char = _peek(-1); if (_peek() == quote_char && _peek(1) == quote_char) { is_multiline = true; @@ -747,7 +793,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - CharType ch = _peek(); + char32_t ch = _peek(); if (ch == '\\') { // Escape pattern. @@ -757,13 +803,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } // Grab escape character. - CharType code = _peek(); + char32_t code = _peek(); _advance(); if (_is_at_end()) { return make_error("Unterminated string."); } - CharType escaped = 0; + char32_t escaped = 0; bool valid_escape = true; switch (code) { @@ -804,8 +850,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - CharType digit = _peek(); - CharType value = 0; + char32_t digit = _peek(); + char32_t value = 0; if (digit >= '0' && digit <= '9') { value = digit - '0'; } else if (digit >= 'a' && digit <= 'f') { @@ -866,6 +912,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); _advance(); break; + } else { + // Not a multiline string termination, add consumed quote. + result += quote_char; } } else { // Ended single-line string. @@ -908,7 +957,7 @@ void GDScriptTokenizer::check_indent() { } for (;;) { - CharType current_indent_char = _peek(); + char32_t current_indent_char = _peek(); int indent_count = 0; if (current_indent_char != ' ' && current_indent_char != '\t' && current_indent_char != '\r' && current_indent_char != '\n' && current_indent_char != '#') { @@ -938,7 +987,7 @@ void GDScriptTokenizer::check_indent() { // Check indent level. bool mixed = false; while (!_is_at_end()) { - CharType space = _peek(); + char32_t space = _peek(); if (space == '\t') { // Consider individual tab columns. column += tab_size - 1; @@ -982,9 +1031,17 @@ void GDScriptTokenizer::check_indent() { } if (_peek() == '#') { // Comment. Advance to the next line. +#ifdef TOOLS_ENABLED + String comment; + while (_peek() != '\n' && !_is_at_end()) { + comment += _advance(); + } + comments[line] = CommentData(comment, true); +#else while (_peek() != '\n' && !_is_at_end()) { _advance(); } +#endif // TOOLS_ENABLED if (_is_at_end()) { // Reached the end with an empty line, so just dedent as much as needed. pending_indents -= indent_level(); @@ -1007,7 +1064,7 @@ void GDScriptTokenizer::check_indent() { // First time indenting, choose character now. indent_char = current_indent_char; } else if (current_indent_char != indent_char) { - Token error = make_error(vformat("Used \"%c\" for indentation instead \"%c\" as used before in the file.", String(¤t_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape())); + Token error = make_error(vformat("Used \"%s\" for indentation instead \"%s\" as used before in the file.", String(¤t_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape())); error.start_line = line; error.start_column = 1; error.leftmost_column = 1; @@ -1071,7 +1128,7 @@ void GDScriptTokenizer::_skip_whitespace() { } for (;;) { - CharType c = _peek(); + char32_t c = _peek(); switch (c) { case ' ': _advance(); @@ -1093,18 +1150,26 @@ void GDScriptTokenizer::_skip_whitespace() { newline(!is_bol); // Don't create new line token if line is empty. check_indent(); break; - case '#': + case '#': { // Comment. +#ifdef TOOLS_ENABLED + String comment; + while (_peek() != '\n' && !_is_at_end()) { + comment += _advance(); + } + comments[line] = CommentData(comment, is_bol); +#else while (_peek() != '\n' && !_is_at_end()) { _advance(); } +#endif // TOOLS_ENABLED if (_is_at_end()) { return; } _advance(); // Consume '\n' newline(!is_bol); check_indent(); - break; + } break; default: return; } @@ -1121,7 +1186,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (pending_newline) { pending_newline = false; if (!multiline_mode) { - // Don't return newline tokens on multine mode. + // Don't return newline tokens on multiline mode. return last_newline; } } @@ -1160,7 +1225,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { return make_token(Token::TK_EOF); } - const CharType c = _advance(); + const char32_t c = _advance(); if (c == '\\') { // Line continuation with backslash. diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 100ed3f132..84b82c07f0 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,10 +31,11 @@ #ifndef GDSCRIPT_TOKENIZER_H #define GDSCRIPT_TOKENIZER_H -#include "core/list.h" -#include "core/set.h" -#include "core/variant.h" -#include "core/vector.h" +#include "core/templates/list.h" +#include "core/templates/map.h" +#include "core/templates/set.h" +#include "core/templates/vector.h" +#include "core/variant/variant.h" class GDScriptTokenizer { public: @@ -177,20 +178,34 @@ public: } Token() { - type = EMPTY; } }; +#ifdef TOOLS_ENABLED + struct CommentData { + String comment; + bool new_line = false; + CommentData() {} + CommentData(const String &p_comment, bool p_new_line) { + comment = p_comment; + new_line = p_new_line; + } + }; + const Map<int, CommentData> &get_comments() const { + return comments; + } +#endif // TOOLS_ENABLED + private: String source; - const CharType *_source = nullptr; - const CharType *_current = nullptr; + const char32_t *_source = nullptr; + const char32_t *_current = nullptr; int line = -1, column = -1; int cursor_line = -1, cursor_column = -1; int tab_size = 4; // Keep track of multichar tokens. - const CharType *_start = nullptr; + const char32_t *_start = nullptr; int start_line = 0, start_column = 0; int leftmost_column = 0, rightmost_column = 0; @@ -202,30 +217,35 @@ private: Token last_newline; int pending_indents = 0; List<int> indent_stack; - List<CharType> paren_stack; - CharType indent_char = '\0'; + List<List<int>> indent_stack_stack; // For lambdas, which require manipulating the indentation point. + List<char32_t> paren_stack; + char32_t indent_char = '\0'; int position = 0; int length = 0; +#ifdef TOOLS_ENABLED + Map<int, CommentData> comments; +#endif // TOOLS_ENABLED + _FORCE_INLINE_ bool _is_at_end() { return position >= length; } - _FORCE_INLINE_ CharType _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } + _FORCE_INLINE_ char32_t _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } int indent_level() const { return indent_stack.size(); } - bool has_error() const { return !error_stack.empty(); } + bool has_error() const { return !error_stack.is_empty(); } Token pop_error(); - CharType _advance(); + char32_t _advance(); void _skip_whitespace(); void check_indent(); Token make_error(const String &p_message); void push_error(const String &p_message); void push_error(const Token &p_error); - Token make_paren_error(CharType p_paren); + Token make_paren_error(char32_t p_paren); Token make_token(Token::Type p_type); Token make_literal(const Variant &p_literal); Token make_identifier(const StringName &p_identifier); - Token check_vcs_marker(CharType p_test, Token::Type p_double_type); - void push_paren(CharType p_char); - bool pop_paren(CharType p_expected); + Token check_vcs_marker(char32_t p_test, Token::Type p_double_type); + void push_paren(char32_t p_char); + bool pop_paren(char32_t p_expected); void newline(bool p_make_token); Token number(); @@ -244,6 +264,8 @@ public: void set_multiline_mode(bool p_state); bool is_past_cursor() const; static String get_token_name(Token::Type p_token_type); + void push_expression_indented_block(); // For lambdas, or blocks inside expressions. + void pop_expression_indented_block(); // For lambdas, or blocks inside expressions. GDScriptTokenizer(); }; diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp new file mode 100644 index 0000000000..64c629662c --- /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.reverse(); + + 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..4757ec6ca9 --- /dev/null +++ b/modules/gdscript/gdscript_vm.cpp @@ -0,0 +1,3239 @@ +/*************************************************************************/ +/* 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" +#include "gdscript_lambda_callable.h" + +Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, 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_STACK: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _stack_size, nullptr); +#endif + return &p_stack[address]; + } break; + case ADDR_TYPE_CONSTANT: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _constant_count, nullptr); +#endif + return &_constants_ptr[address]; + } 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; + } + + ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); + return nullptr; +} + +#ifdef DEBUG_ENABLED +static String _get_script_name(const Ref<Script> p_script) { + Ref<GDScript> gdscript = p_script; + if (gdscript.is_valid()) { + return gdscript->get_script_class_name(); + } else if (p_script->get_name().is_empty()) { + return p_script->get_path().get_file(); + } else { + return p_script->get_name(); + } +} + +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 { + basestr = bobj->get_class(); + if (bobj->get_script_instance()) { + basestr += " (" + _get_script_name(bobj->get_script_instance()->get_script()) + ")"; + } + } + + } else { + if (p_var->get_type() == Variant::ARRAY) { + basestr = "Array"; + const Array *p_array = VariantInternal::get_array(p_var); + Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin(); + StringName native_type = p_array->get_typed_class_name(); + Ref<Script> script_type = p_array->get_typed_script(); + + if (script_type.is_valid() && script_type->is_valid()) { + basestr += "[" + _get_script_name(script_type) + "]"; + } else if (native_type != StringName()) { + basestr += "[" + native_type.operator String() + "]"; + } else if (builtin_type != Variant::NIL) { + basestr += "[" + Variant::get_type_name(builtin_type) + "]"; + } + } 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_ARRAY, \ + &&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_TYPED_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_CREATE_LAMBDA, \ + &&OPCODE_JUMP, \ + &&OPCODE_JUMP_IF, \ + &&OPCODE_JUMP_IF_NOT, \ + &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ + &&OPCODE_RETURN, \ + &&OPCODE_RETURN_TYPED_BUILTIN, \ + &&OPCODE_RETURN_TYPED_ARRAY, \ + &&OPCODE_RETURN_TYPED_NATIVE, \ + &&OPCODE_RETURN_TYPED_SCRIPT, \ + &&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_STORE_NAMED_GLOBAL, \ + &&OPCODE_TYPE_ADJUST_BOOL, \ + &&OPCODE_TYPE_ADJUST_INT, \ + &&OPCODE_TYPE_ADJUST_FLOAT, \ + &&OPCODE_TYPE_ADJUST_STRING, \ + &&OPCODE_TYPE_ADJUST_VECTOR2, \ + &&OPCODE_TYPE_ADJUST_VECTOR2I, \ + &&OPCODE_TYPE_ADJUST_RECT2, \ + &&OPCODE_TYPE_ADJUST_RECT2I, \ + &&OPCODE_TYPE_ADJUST_VECTOR3, \ + &&OPCODE_TYPE_ADJUST_VECTOR3I, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \ + &&OPCODE_TYPE_ADJUST_PLANE, \ + &&OPCODE_TYPE_ADJUST_QUAT, \ + &&OPCODE_TYPE_ADJUST_AABB, \ + &&OPCODE_TYPE_ADJUST_BASIS, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM, \ + &&OPCODE_TYPE_ADJUST_COLOR, \ + &&OPCODE_TYPE_ADJUST_STRING_NAME, \ + &&OPCODE_TYPE_ADJUST_NODE_PATH, \ + &&OPCODE_TYPE_ADJUST_RID, \ + &&OPCODE_TYPE_ADJUST_OBJECT, \ + &&OPCODE_TYPE_ADJUST_CALLABLE, \ + &&OPCODE_TYPE_ADJUST_SIGNAL, \ + &&OPCODE_TYPE_ADJUST_DICTIONARY, \ + &&OPCODE_TYPE_ADJUST_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \ + &&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 retvalue; + Variant *stack = nullptr; + Variant **instruction_args = nullptr; + 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; + + } 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; + } + } + + // Add 3 here for self, class, and nil. + alloca_size = sizeof(Variant *) * 3 + sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size; + + uint8_t *aptr = (uint8_t *)alloca(alloca_size); + stack = (Variant *)aptr; + + for (int i = 0; i < p_argcount; i++) { + if (!argument_types[i].has_type) { + memnew_placement(&stack[i + 3], 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 + 3], Variant(arg)); + } else { + memnew_placement(&stack[i + 3], Variant(*p_args[i])); + } + } + for (int i = p_argcount + 3; i < _stack_size; i++) { + memnew_placement(&stack[i], Variant); + } + + memnew_placement(&stack[ADDR_STACK_NIL], Variant); + + if (_instruction_args_size) { + instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; + } else { + instruction_args = nullptr; + } + + if (p_instance) { + memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner)); + script = p_instance->script.ptr(); + } else { + memnew_placement(&stack[ADDR_STACK_SELF], Variant); + script = _script; + } + } + if (_ptrcall_args_size) { + call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *)); + } else { + call_args_ptr = nullptr; + } + + memnew_placement(&stack[ADDR_STACK_CLASS], Variant(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, 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, 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] & INSTR_MASK; +#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 should 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_ARRAY) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + + Array *dst_arr = VariantInternal::get_array(dst); + + if (src->get_type() != Variant::ARRAY) { +#ifdef DEBUG_ENABLED + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + +"'."; +#endif + OPCODE_BREAK; + } + if (!dst_arr->typed_assign(*src)) { +#ifdef DEBUG_ENABLED + err_text = "Trying to assign a typed array with an array of different type.'"; +#endif + OPCODE_BREAK; + } + + ip += 3; + } + 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 = Variant(); // Clear potential previous typed array. + + *dst = array; + + ip += 2; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_CONSTRUCT_TYPED_ARRAY) { + CHECK_SPACE(3 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + GET_INSTRUCTION_ARG(script_type, argc + 1); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 2]; + int native_type_idx = _code_ptr[ip + 3]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; + + Array array; + array.set_typed(builtin_type, native_type, script_type); + array.resize(argc); + + for (int i = 0; i < argc; i++) { + array[i] = *(instruction_args[i]); + } + + GET_INSTRUCTION_ARG(dst, argc); + *dst = Variant(); // Clear potential previous typed array. + + *dst = array; + + ip += 4; + } + 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); + bool is_callable = false; + + if (methodstr == "call") { + if (argc >= 1 && base->get_type() != Variant::CALLABLE) { + methodstr = String(*argptrs[0]) + " (via call)"; + if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { + err.argument += 1; + } + } else { + methodstr = base->operator String() + " (Callable)"; + is_callable = true; + } + } 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 + (is_callable ? "" : "' 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.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_CREATE_LAMBDA) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int captures_count = _code_ptr[ip + 1]; + GD_ERR_BREAK(captures_count < 0); + + int lambda_index = _code_ptr[ip + 2]; + GD_ERR_BREAK(lambda_index < 0 || lambda_index >= _lambdas_count); + GDScriptFunction *lambda = _lambdas_ptr[lambda_index]; + + Vector<Variant> captures; + captures.resize(captures_count); + for (int i = 0; i < captures_count; i++) { + GET_INSTRUCTION_ARG(arg, i); + captures.write[i] = *arg; + } + + GDScriptLambdaCallable *callable = memnew(GDScriptLambdaCallable(Ref<GDScript>(script), lambda, captures)); + + GET_INSTRUCTION_ARG(result, captures_count); + *result = Callable(callable); + + ip += 3; + } + 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_RETURN_TYPED_BUILTIN) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(r, 0); + + Variant::Type ret_type = (Variant::Type)_code_ptr[ip + 2]; + GD_ERR_BREAK(ret_type < 0 || ret_type >= Variant::VARIANT_MAX); + + if (r->get_type() != ret_type) { + if (Variant::can_convert_strict(r->get_type(), ret_type)) { + Callable::CallError ce; + Variant::construct(ret_type, retvalue, const_cast<const Variant **>(&r), 1, ce); + } else { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + Variant::get_type_name(r->get_type()), Variant::get_type_name(ret_type)); +#endif // DEBUG_ENABLED + + // Construct a base type anyway so type constraints are met. + Callable::CallError ce; + Variant::construct(ret_type, retvalue, nullptr, 0, ce); + OPCODE_BREAK; + } + } else { + retvalue = *r; + } +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + OPCODE(OPCODE_RETURN_TYPED_ARRAY) { + CHECK_SPACE(5); + GET_INSTRUCTION_ARG(r, 0); + + GET_INSTRUCTION_ARG(script_type, 1); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3]; + int native_type_idx = _code_ptr[ip + 4]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; + + if (r->get_type() != Variant::ARRAY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "Array[%s]".)", + Variant::get_type_name(r->get_type()), Variant::get_type_name(builtin_type)); +#endif + OPCODE_BREAK; + } + + Array array; + array.set_typed(builtin_type, native_type, script_type); + +#ifdef DEBUG_ENABLED + bool valid = array.typed_assign(*VariantInternal::get_array(r)); +#else + array.typed_assign(*VariantInternal::get_array(r)); +#endif // DEBUG_ENABLED + + // Assign the return value anyway since we want it to be the valid type. + retvalue = array; + +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Trying to return a typed array with an array of different type.'"; + OPCODE_BREAK; + } + + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + OPCODE(OPCODE_RETURN_TYPED_NATIVE) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(r, 0); + + GET_INSTRUCTION_ARG(type, 1); + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); + GD_ERR_BREAK(!nc); + + if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) { + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + Variant::get_type_name(r->get_type()), nc->get_name()); + OPCODE_BREAK; + } + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *ret_obj = r->get_validated_object_with_check(freed); + + if (freed) { + err_text = "Trying to return a previously freed instance."; + OPCODE_BREAK; + } +#else + Object *ret_obj = r->operator Object *(); +#endif // DEBUG_ENABLED + if (ret_obj && !ClassDB::is_parent_class(ret_obj->get_class_name(), nc->get_name())) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + ret_obj->get_class_name(), nc->get_name()); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + retvalue = *r; + +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + OPCODE(OPCODE_RETURN_TYPED_SCRIPT) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(r, 0); + + GET_INSTRUCTION_ARG(type, 1); + Script *base_type = Object::cast_to<Script>(type->operator Object *()); + GD_ERR_BREAK(!base_type); + + if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + Variant::get_type_name(r->get_type()), _get_script_name(Ref<Script>(base_type))); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *ret_obj = r->get_validated_object_with_check(freed); + + if (freed) { + err_text = "Trying to return a previously freed instance."; + OPCODE_BREAK; + } +#else + Object *ret_obj = r->operator Object *(); +#endif // DEBUG_ENABLED + + if (ret_obj) { + ScriptInstance *ret_inst = ret_obj->get_script_instance(); + if (!ret_inst) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + ret_obj->get_class_name(), _get_script_name(Ref<GDScript>(base_type))); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Script *ret_type = ret_obj->get_script_instance()->get_script().ptr(); + bool valid = false; + + while (ret_type) { + if (ret_type == base_type) { + valid = true; + break; + } + ret_type = ret_type->get_base_script().ptr(); + } + + if (!valid) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + _get_script_name(ret_obj->get_script_instance()->get_script()), _get_script_name(Ref<GDScript>(base_type))); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + } + retvalue = *r; + +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + 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_STORE_NAMED_GLOBAL) { + CHECK_SPACE(3); + int globalname_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(globalname_idx < 0 || globalname_idx >= _global_names_count); + const StringName *globalname = &_global_names_ptr[globalname_idx]; + + GET_INSTRUCTION_ARG(dst, 0); + *dst = GDScriptLanguage::get_singleton()->get_named_globals_map()[*globalname]; + + ip += 3; + } + DISPATCH_OPCODE; + +#define OPCODE_TYPE_ADJUST(m_v_type, m_c_type) \ + OPCODE(OPCODE_TYPE_ADJUST_##m_v_type) { \ + CHECK_SPACE(2); \ + GET_INSTRUCTION_ARG(arg, 0); \ + VariantTypeAdjust<m_c_type>::adjust(arg); \ + ip += 2; \ + } \ + DISPATCH_OPCODE + + OPCODE_TYPE_ADJUST(BOOL, bool); + OPCODE_TYPE_ADJUST(INT, int64_t); + OPCODE_TYPE_ADJUST(FLOAT, double); + OPCODE_TYPE_ADJUST(STRING, String); + OPCODE_TYPE_ADJUST(VECTOR2, Vector2); + OPCODE_TYPE_ADJUST(VECTOR2I, Vector2i); + OPCODE_TYPE_ADJUST(RECT2, Rect2); + OPCODE_TYPE_ADJUST(RECT2I, Rect2i); + OPCODE_TYPE_ADJUST(VECTOR3, Vector3); + OPCODE_TYPE_ADJUST(VECTOR3I, Vector3i); + OPCODE_TYPE_ADJUST(TRANSFORM2D, Transform2D); + OPCODE_TYPE_ADJUST(PLANE, Plane); + OPCODE_TYPE_ADJUST(QUAT, Quat); + OPCODE_TYPE_ADJUST(AABB, AABB); + OPCODE_TYPE_ADJUST(BASIS, Basis); + OPCODE_TYPE_ADJUST(TRANSFORM, Transform); + OPCODE_TYPE_ADJUST(COLOR, Color); + OPCODE_TYPE_ADJUST(STRING_NAME, StringName); + OPCODE_TYPE_ADJUST(NODE_PATH, NodePath); + OPCODE_TYPE_ADJUST(RID, RID); + OPCODE_TYPE_ADJUST(OBJECT, Object *); + OPCODE_TYPE_ADJUST(CALLABLE, Callable); + OPCODE_TYPE_ADJUST(SIGNAL, Signal); + OPCODE_TYPE_ADJUST(DICTIONARY, Dictionary); + OPCODE_TYPE_ADJUST(ARRAY, Array); + OPCODE_TYPE_ADJUST(PACKED_BYTE_ARRAY, PackedByteArray); + OPCODE_TYPE_ADJUST(PACKED_INT32_ARRAY, PackedInt32Array); + OPCODE_TYPE_ADJUST(PACKED_INT64_ARRAY, PackedInt64Array); + OPCODE_TYPE_ADJUST(PACKED_FLOAT32_ARRAY, PackedFloat32Array); + OPCODE_TYPE_ADJUST(PACKED_FLOAT64_ARRAY, PackedFloat64Array); + OPCODE_TYPE_ADJUST(PACKED_STRING_ARRAY, PackedStringArray); + OPCODE_TYPE_ADJUST(PACKED_VECTOR2_ARRAY, PackedVector2Array); + OPCODE_TYPE_ADJUST(PACKED_VECTOR3_ARRAY, PackedVector3Array); + OPCODE_TYPE_ADJUST(PACKED_COLOR_ARRAY, PackedColorArray); + + 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 index 105facd9d0..ad41b60a4e 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.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,7 +30,7 @@ #include "gdscript_warning.h" -#include "core/variant.h" +#include "core/variant/variant.h" #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index e183d6f302..4b295b5eb8 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.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,8 +33,8 @@ #ifdef DEBUG_ENABLED -#include "core/ustring.h" -#include "core/vector.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" class GDScriptWarning { public: diff --git a/modules/gdscript/icons/GDScript.svg b/modules/gdscript/icons/GDScript.svg index 953bb9ae9e..aa59125ea9 100644 --- a/modules/gdscript/icons/GDScript.svg +++ b/modules/gdscript/icons/GDScript.svg @@ -1,5 +1 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<path transform="translate(0 1036.4)" d="m7 1l-0.56445 2.2578a5 5 0 0 0 -0.68945 0.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -0.28516 0.68555l-2.2539 0.5625v2l2.2578 0.56445a5 5 0 0 0 0.2793 0.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 0.68555 0.28516l0.5625 2.2539h2l0.56445-2.2578a5 5 0 0 0 0.6875 -0.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 0.28516 -0.68555l2.2539-0.5625v-2l-2.2578-0.56445a5 5 0 0 0 -0.2793 -0.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -0.68555 -0.28516l-0.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2z" fill="#e0e0e0"/> -</g> -</svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2l2.2578.56445a5 5 0 0 0 .2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 .68555.28516l.5625 2.2539h2l.56445-2.2578a5 5 0 0 0 .6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 .28516-.68555l2.2539-.5625v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0"/></svg> diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 4d79d9d395..15236d900d 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 */ @@ -49,8 +49,9 @@ void ExtendGDScriptParser::update_diagnostics() { diagnostic.code = -1; lsp::Range range; lsp::Position pos; - int line = LINE_NUMBER_TO_INDEX(error.line); - const String &line_text = get_lines()[line]; + const PackedStringArray lines = get_lines(); + int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, lines.size() - 1); + const String &line_text = lines[line]; pos.line = line; pos.character = line_text.length() - line_text.strip_edges(true, false).length(); range.start = pos; @@ -147,7 +148,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.script_path = path; r_symbol.children.clear(); r_symbol.name = p_class->identifier != nullptr ? String(p_class->identifier->name) : String(); - if (r_symbol.name.empty()) { + if (r_symbol.name.is_empty()) { r_symbol.name = path.get_file(); } r_symbol.kind = lsp::SymbolKind::Class; @@ -215,9 +216,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p String value_text; if (default_value.get_type() == Variant::OBJECT) { RES res = default_value; - if (res.is_valid() && !res->get_path().empty()) { + if (res.is_valid() && !res->get_path().is_empty()) { value_text = "preload(\"" + res->get_path() + "\")"; - if (symbol.documentation.empty()) { + 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; } @@ -228,7 +229,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } else { value_text = JSON::print(default_value); } - if (!value_text.empty()) { + if (!value_text.is_empty()) { symbol.detail += " = " + value_text; } @@ -361,24 +362,73 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN r_symbol.detail += " -> " + p_func->get_datatype().to_string(); } - for (int i = 0; i < p_func->body->locals.size(); i++) { - const SuiteNode::Local &local = p_func->body->locals[i]; - lsp::DocumentSymbol symbol; - 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 = SuiteNode::Local::CONSTANT ? "const " : "var "; - symbol.detail += symbol.name; - if (local.get_datatype().is_hard_type()) { - symbol.detail += ": " + local.get_datatype().to_string(); + List<GDScriptParser::SuiteNode *> function_nodes; + + List<GDScriptParser::Node *> node_stack; + node_stack.push_back(p_func->body); + + while (!node_stack.is_empty()) { + GDScriptParser::Node *node = node_stack[0]; + node_stack.pop_front(); + + switch (node->type) { + case GDScriptParser::TypeNode::IF: { + GDScriptParser::IfNode *if_node = (GDScriptParser::IfNode *)node; + node_stack.push_back(if_node->true_block); + if (if_node->false_block) { + node_stack.push_back(if_node->false_block); + } + } break; + + case GDScriptParser::TypeNode::FOR: { + GDScriptParser::ForNode *for_node = (GDScriptParser::ForNode *)node; + node_stack.push_back(for_node->loop); + } break; + + case GDScriptParser::TypeNode::WHILE: { + GDScriptParser::WhileNode *while_node = (GDScriptParser::WhileNode *)node; + node_stack.push_back(while_node->loop); + } break; + + case GDScriptParser::TypeNode::MATCH_BRANCH: { + GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node; + node_stack.push_back(match_node->block); + } break; + + case GDScriptParser::TypeNode::SUITE: { + GDScriptParser::SuiteNode *suite_node = (GDScriptParser::SuiteNode *)node; + function_nodes.push_back(suite_node); + for (int i = 0; i < suite_node->statements.size(); ++i) { + node_stack.push_back(suite_node->statements[i]); + } + } break; + + default: + continue; + } + } + + for (List<GDScriptParser::SuiteNode *>::Element *N = function_nodes.front(); N; N = N->next()) { + const GDScriptParser::SuiteNode *suite_node = N->get(); + for (int i = 0; i < suite_node->locals.size(); i++) { + const SuiteNode::Local &local = suite_node->locals[i]; + lsp::DocumentSymbol symbol; + 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 = local.type == 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_NUMBER_TO_INDEX(local.start_line)); + r_symbol.children.push_back(symbol); } - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line)); - r_symbol.children.push_back(symbol); } } @@ -453,7 +503,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c 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); @@ -491,7 +541,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int start_pos = p_position.character; for (int c = p_position.character; c >= 0; c--) { start_pos = c; - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -500,7 +550,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -552,7 +602,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 == '(') { @@ -589,7 +639,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; @@ -611,7 +661,7 @@ 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); @@ -723,8 +773,8 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode } break; case ClassNode::Member::ENUM: { Dictionary enum_dict; - for (int j = 0; j < m.m_enum->values.size(); i++) { - enum_dict[m.m_enum->values[i].identifier->name] = m.m_enum->values[i].value; + for (int j = 0; j < m.m_enum->values.size(); j++) { + enum_dict[m.m_enum->values[j].identifier->name] = m.m_enum->values[j].value; } Dictionary api; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 0c031d7883..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 diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 2a67d2ff4f..c16a7fa889 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 */ @@ -30,9 +30,9 @@ #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" @@ -95,7 +95,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()); } } @@ -104,7 +104,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); @@ -140,7 +140,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); @@ -162,7 +162,7 @@ void GDScriptLanguageProtocol::_bind_methods() { ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized); ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected); ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected); - ClassDB::bind_method(D_METHOD("notify_client", "method", "params"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("notify_client", "method", "params", "client_id"), &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); @@ -212,7 +212,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { void GDScriptLanguageProtocol::initialized(const Variant &p_params) { lsp::GodotCapabilities capabilities; - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { lsp::GodotNativeClassInfo gdclass; gdclass.name = E->get().name; @@ -255,7 +255,7 @@ void GDScriptLanguageProtocol::poll() { } } -Error GDScriptLanguageProtocol::start(int p_port, const IP_Address &p_bind_ip) { +Error GDScriptLanguageProtocol::start(int p_port, const IPAddress &p_bind_ip) { return server->listen(p_port, p_bind_ip); } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index cf5242e8c5..a5c5a233b1 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 */ @@ -69,7 +69,7 @@ private: static GDScriptLanguageProtocol *singleton; HashMap<int, Ref<LSPeer>> clients; - Ref<TCP_Server> server; + Ref<TCPServer> server; int latest_client_id = 0; int next_client_id = 0; @@ -97,7 +97,7 @@ public: _FORCE_INLINE_ bool is_initialized() const { return _initialized; } void poll(); - Error start(int p_port, const IP_Address &p_bind_ip); + Error start(int p_port, const IPAddress &p_bind_ip); void stop(); void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 3387d262f8..340a7b9343 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 */ @@ -36,12 +36,6 @@ #include "editor/editor_node.h" GDScriptLanguageServer::GDScriptLanguageServer() { - thread = nullptr; - thread_running = false; - started = false; - - use_thread = false; - port = 6008; _EDITOR_DEF("network/language_server/remote_port", port); _EDITOR_DEF("network/language_server/enable_smart_resolve", true); _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false); @@ -84,12 +78,11 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { port = (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (protocol.start(port, IP_Address("127.0.0.1")) == OK) { + if (protocol.start(port, IPAddress("127.0.0.1")) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { - ERR_FAIL_COND(thread != nullptr); thread_running = true; - thread = Thread::create(GDScriptLanguageServer::thread_main, this); + thread.start(GDScriptLanguageServer::thread_main, this); } set_process_internal(!use_thread); started = true; @@ -98,11 +91,9 @@ void GDScriptLanguageServer::start() { void GDScriptLanguageServer::stop() { if (use_thread) { - ERR_FAIL_COND(nullptr == thread); + ERR_FAIL_COND(!thread.is_started()); thread_running = false; - Thread::wait_to_finish(thread); - memdelete(thread); - thread = nullptr; + thread.wait_to_finish(); } protocol.stop(); started = false; diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 228d29bf42..29c5ddd70e 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 */ @@ -40,11 +40,11 @@ class GDScriptLanguageServer : public EditorPlugin { GDScriptLanguageProtocol protocol; - Thread *thread; - bool thread_running; - bool started; - bool use_thread; - int port; + Thread thread; + bool thread_running = false; + bool started = false; + bool use_thread = false; + int port = 6008; static void thread_main(void *p_userdata); private: diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index c6fe3169dc..030633274c 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 */ @@ -147,7 +147,7 @@ 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()); @@ -257,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) { @@ -341,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; @@ -400,6 +400,7 @@ GDScriptTextDocument::~GDScriptTextDocument() { void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); + EditorFileSystem::get_singleton()->update_file(path); } void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { @@ -425,7 +426,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 776193e37c..9b7b2b36b4 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 */ @@ -32,8 +32,9 @@ #include "../gdscript.h" #include "../gdscript_parser.h" -#include "core/project_settings.h" -#include "core/script_language.h" +#include "core/config/project_settings.h" +#include "core/object/script_language.h" +#include "editor/doc_tools.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_node.h" @@ -41,6 +42,7 @@ #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { + ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files); ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol); 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); @@ -50,6 +52,16 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } +void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) { + Array files = p_params["files"]; + for (int i = 0; i < files.size(); ++i) { + Dictionary file = files[i]; + String uri = file["uri"]; + String path = get_file_path(uri); + parse_script(path, ""); + } +} + void GDScriptWorkspace::remove_cache_parser(const String &p_path) { Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path); Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path); @@ -79,7 +91,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++) { @@ -170,7 +182,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); @@ -189,7 +201,7 @@ Error GDScriptWorkspace::initialize() { return OK; } - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { const DocData::ClassDoc &class_data = E->value(); lsp::DocumentSymbol class_symbol; @@ -198,7 +210,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; @@ -262,7 +274,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; @@ -277,11 +289,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; @@ -349,7 +361,7 @@ Error GDScriptWorkspace::parse_local_script(const String &p_path) { String GDScriptWorkspace::get_file_path(const String &p_uri) const { String path = p_uri; path = path.replace(root_uri + "/", "res://"); - path = path.http_unescape(); + path = path.uri_decode(); return path; } @@ -447,13 +459,13 @@ 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); @@ -473,7 +485,7 @@ 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); @@ -528,7 +540,7 @@ 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; } diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index e45b06747d..27616a2989 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" @@ -89,6 +89,7 @@ public: void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list); Dictionary generate_script_api(const String &p_path); Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature); + void did_delete_files(const Dictionary &p_params); GDScriptWorkspace(); ~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index cf27a1578c..47bcfeaefc 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 { @@ -547,7 +547,7 @@ struct TextDocumentItem { * The version number of this document (it will increase after each * change, including undo/redo). */ - int version; + int version = 0; /** * The content of the opened text document. @@ -584,7 +584,7 @@ struct TextDocumentContentChangeEvent { /** * The length of the range that got replaced. */ - int rangeLength; + int rangeLength = 0; /** * The new text of the range/document. @@ -656,12 +656,12 @@ struct Diagnostic { * The diagnostic's severity. Can be omitted. If omitted it is up to the * client to interpret diagnostics as error, warning, info or hint. */ - int severity; + int severity = 0; /** * The diagnostic's code, which might appear in the user interface. */ - int code; + int code = 0; /** * A human-readable string describing the source of this @@ -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++) { @@ -833,7 +833,7 @@ struct CompletionItem { * an icon is chosen by the editor. The standardized set * of available values is defined in `CompletionItemKind`. */ - int kind; + int kind = 0; /** * A human-readable string with additional information @@ -891,7 +891,7 @@ struct CompletionItem { * The format of the insert text. The format applies to both the `insertText` property * and the `newText` property of a provided `textEdit`. */ - int insertTextFormat; + int insertTextFormat = 0; /** * An edit which is applied to a document when selecting this completion. When an edit is provided the value of @@ -1003,7 +1003,7 @@ struct CompletionList { * This list it not complete. Further typing should result in recomputing * this list. */ - bool isIncomplete; + bool isIncomplete = false; /** * The completion items. @@ -1191,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; @@ -1528,6 +1528,114 @@ struct SignatureHelp { } }; +/** + * A pattern to describe in which file operation requests or notifications + * the server is interested in. + */ +struct FileOperationPattern { + /** + * The glob pattern to match. + */ + String glob = "**/*.gd"; + + /** + * Whether to match `file`s or `folder`s with this pattern. + * + * Matches both if undefined. + */ + String matches = "file"; + + Dictionary to_json() const { + Dictionary dict; + + dict["glob"] = glob; + dict["matches"] = matches; + + return dict; + } +}; + +/** + * A filter to describe in which file operation requests or notifications + * the server is interested in. + */ +struct FileOperationFilter { + /** + * The actual file operation pattern. + */ + FileOperationPattern pattern; + + Dictionary to_json() const { + Dictionary dict; + + dict["pattern"] = pattern.to_json(); + + return dict; + } +}; + +/** + * The options to register for file operations. + */ +struct FileOperationRegistrationOptions { + /** + * The actual filters. + */ + Vector<FileOperationFilter> filters; + + FileOperationRegistrationOptions() { + filters.push_back(FileOperationFilter()); + } + + Dictionary to_json() const { + Dictionary dict; + + Array filts; + for (int i = 0; i < filters.size(); i++) { + filts.push_back(filters[i].to_json()); + } + dict["filters"] = filts; + + return dict; + } +}; + +/** + * The server is interested in file notifications/requests. + */ +struct FileOperations { + /** + * The server is interested in receiving didDeleteFiles file notifications. + */ + FileOperationRegistrationOptions didDelete; + + Dictionary to_json() const { + Dictionary dict; + + dict["didDelete"] = didDelete.to_json(); + + return dict; + } +}; + +/** + * Workspace specific server capabilities + */ +struct Workspace { + /** + * The server is interested in file notifications/requests. + */ + FileOperations fileOperations; + + Dictionary to_json() const { + Dictionary dict; + + dict["fileOperations"] = fileOperations.to_json(); + + return dict; + } +}; + struct ServerCapabilities { /** * Defines how text documents are synced. Is either a detailed structure defining each notification or @@ -1590,6 +1698,11 @@ struct ServerCapabilities { bool workspaceSymbolProvider = true; /** + * The server supports workspace folder. + */ + Workspace workspace; + + /** * The server provides code actions. The `CodeActionOptions` return type is only * valid if the client signals code action literal support via the property * `textDocument.codeAction.codeActionLiteralSupport`. @@ -1661,7 +1774,7 @@ struct ServerCapabilities { signatureHelpProvider.triggerCharacters.push_back(","); signatureHelpProvider.triggerCharacters.push_back("("); dict["signatureHelpProvider"] = signatureHelpProvider.to_json(); - dict["codeLensProvider"] = false; // codeLensProvider.to_json(); + //dict["codeLensProvider"] = codeLensProvider.to_json(); dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json(); dict["renameProvider"] = renameProvider.to_json(); dict["documentLinkProvider"] = documentLinkProvider.to_json(); @@ -1676,6 +1789,7 @@ struct ServerCapabilities { dict["documentHighlightProvider"] = documentHighlightProvider; dict["documentSymbolProvider"] = documentSymbolProvider; dict["workspaceSymbolProvider"] = workspaceSymbolProvider; + dict["workspace"] = workspace.to_json(); dict["codeActionProvider"] = codeActionProvider; dict["documentFormattingProvider"] = documentFormattingProvider; dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider; @@ -1781,7 +1895,6 @@ static String marked_documentation(const String &p_bbcode) { } return markdown; } - } // namespace lsp #endif diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index c554cbac05..2d2f94f5e0 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 */ @@ -38,6 +38,12 @@ #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; @@ -54,7 +60,7 @@ GDScriptCache *gdscript_cache = nullptr; #include "editor/gdscript_translation_parser_plugin.h" #ifndef GDSCRIPT_NO_LSP -#include "core/engine.h" +#include "core/config/engine.h" #include "language_server/gdscript_language_server.h" #endif // !GDSCRIPT_NO_LSP @@ -79,7 +85,7 @@ public: return; } - // TODO: Readd compiled/encrypted GDScript on export. + // TODO: Re-add compiled GDScript on export. return; } }; @@ -107,7 +113,6 @@ static void _editor_init() { void register_gdscript_types() { ClassDB::register_class<GDScript>(); - ClassDB::register_virtual_class<GDScriptFunctionState>(); script_language_gd = memnew(GDScriptLanguage); ScriptServer::register_language(script_language_gd); @@ -126,6 +131,8 @@ void register_gdscript_types() { 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() { @@ -151,5 +158,28 @@ void unregister_gdscript_types() { #endif // TOOLS_ENABLED GDScriptParser::cleanup(); - GDScriptAnalyzer::cleanup(); + GDScriptUtilityFunctions::unregister_functions(); +} + +#ifdef TESTS_ENABLED +void test_tokenizer() { + GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER); +} + +void test_parser() { + GDScriptTests::test(GDScriptTests::TestType::TEST_PARSER); } + +void test_compiler() { + GDScriptTests::test(GDScriptTests::TestType::TEST_COMPILER); +} + +void test_bytecode() { + GDScriptTests::test(GDScriptTests::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/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp new file mode 100644 index 0000000000..76ae43e792 --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -0,0 +1,584 @@ +/*************************************************************************/ +/* gdscript_test_runner.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_test_runner.h" + +#include "../gdscript.h" +#include "../gdscript_analyzer.h" +#include "../gdscript_compiler.h" +#include "../gdscript_parser.h" + +#include "core/config/project_settings.h" +#include "core/core_string_names.h" +#include "core/io/file_access_pack.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" +#include "core/string/string_builder.h" +#include "scene/resources/packed_scene.h" + +#include "tests/test_macros.h" + +namespace GDScriptTests { + +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 init_language(const String &p_base_path) { + // 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(p_base_path, 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. + GDScriptLanguage::get_singleton()->init(); + init_autoloads(); +} + +void finish_language() { + GDScriptLanguage::get_singleton()->finish(); + ScriptServer::global_classes_clear(); +} + +StringName GDScriptTestRunner::test_function_name; + +GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language) { + test_function_name = StaticCString::create("test"); + do_init_languages = p_init_language; + + source_dir = p_source_dir; + if (!source_dir.ends_with("/")) { + source_dir += "/"; + } + + if (do_init_languages) { + init_language(p_source_dir); + + // Enable all warnings for GDScript, so we can test them. + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); + for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { + String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); + } + } + + // Enable printing to show results + _print_line_enabled = true; + _print_error_enabled = true; +} + +GDScriptTestRunner::~GDScriptTestRunner() { + test_function_name = StringName(); + if (do_init_languages) { + finish_language(); + } +} + +int GDScriptTestRunner::run_tests() { + if (!make_tests()) { + FAIL("An error occurred while making the tests."); + return -1; + } + + if (!generate_class_index()) { + FAIL("An error occurred while generating class index."); + return -1; + } + + int failed = 0; + for (int i = 0; i < tests.size(); i++) { + GDScriptTest test = tests[i]; + GDScriptTest::TestResult result = test.run_test(); + + String expected = FileAccess::get_file_as_string(test.get_output_file()); + INFO(test.get_source_file()); + if (!result.passed) { + INFO(expected); + failed++; + } + + CHECK_MESSAGE(result.passed, (result.passed ? String() : result.output)); + } + + return failed; +} + +bool GDScriptTestRunner::generate_outputs() { + is_generating = true; + + if (!make_tests()) { + print_line("Failed to generate a test output."); + return false; + } + + if (!generate_class_index()) { + return false; + } + + for (int i = 0; i < tests.size(); i++) { + OS::get_singleton()->print("."); + GDScriptTest test = tests[i]; + bool result = test.generate_output(); + + if (!result) { + print_line("\nCould not generate output for " + test.get_source_file()); + return false; + } + } + print_line("\nGenerated output files for " + itos(tests.size()) + " tests successfully."); + + return true; +} + +bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { + Error err = OK; + DirAccessRef dir(DirAccess::open(p_dir, &err)); + + if (err != OK) { + return false; + } + + String current_dir = dir->get_current_dir(); + + dir->list_dir_begin(); + String next = dir->get_next(); + + while (!next.is_empty()) { + if (dir->current_is_dir()) { + if (next == "." || next == "..") { + next = dir->get_next(); + continue; + } + if (!make_tests_for_dir(current_dir.plus_file(next))) { + return false; + } + } else { + if (next.get_extension().to_lower() == "gd") { + String out_file = next.get_basename() + ".out"; + if (!is_generating && !dir->file_exists(out_file)) { + ERR_FAIL_V_MSG(false, "Could not find output file for " + next); + } + GDScriptTest test(current_dir.plus_file(next), current_dir.plus_file(out_file), source_dir); + tests.push_back(test); + } + } + + next = dir->get_next(); + } + + dir->list_dir_end(); + + return true; +} + +bool GDScriptTestRunner::make_tests() { + Error err = OK; + DirAccessRef dir(DirAccess::open(source_dir, &err)); + + ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory."); + + return make_tests_for_dir(dir->get_current_dir()); +} + +bool GDScriptTestRunner::generate_class_index() { + StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name(); + for (int i = 0; i < tests.size(); i++) { + GDScriptTest test = tests[i]; + String base_type; + + String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(test.get_source_file(), &base_type); + if (class_name == String()) { + continue; + } + ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false, + "Class name '" + class_name + "' from " + test.get_source_file() + " is already used in " + ScriptServer::get_global_class_path(class_name)); + + ScriptServer::add_global_class(class_name, base_type, gdscript_name, test.get_source_file()); + } + return true; +} + +GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) { + source_file = p_source_path; + output_file = p_output_path; + base_dir = p_base_dir; + _print_handler.printfunc = print_handler; + _error_handler.errfunc = error_handler; +} + +void GDScriptTestRunner::handle_cmdline() { + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + // TODO: this could likely be ported to use test commands: + // https://github.com/godotengine/godot/pull/41355 + // Currently requires to startup the whole engine, which is slow. + String test_cmd = "--gdscript-test"; + String gen_cmd = "--gdscript-generate-tests"; + + for (List<String>::Element *E = cmdline_args.front(); E != nullptr; E = E->next()) { + String &cmd = E->get(); + if (cmd == test_cmd || cmd == gen_cmd) { + if (E->next() == nullptr) { + ERR_PRINT("Needed a path for the test files."); + exit(-1); + } + + const String &path = E->next()->get(); + + GDScriptTestRunner runner(path, false); + int failed = 0; + if (cmd == test_cmd) { + failed = runner.run_tests(); + } else { + bool completed = runner.generate_outputs(); + failed = completed ? 0 : -1; + } + exit(failed); + } + } +} + +void GDScriptTest::enable_stdout() { + // TODO: this could likely be handled by doctest or `tests/test_macros.h`. + OS::get_singleton()->set_stdout_enabled(true); + OS::get_singleton()->set_stderr_enabled(true); +} + +void GDScriptTest::disable_stdout() { + // TODO: this could likely be handled by doctest or `tests/test_macros.h`. + OS::get_singleton()->set_stdout_enabled(false); + OS::get_singleton()->set_stderr_enabled(false); +} + +void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error) { + TestResult *result = (TestResult *)p_this; + result->output += p_message + "\n"; +} + +void GDScriptTest::error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type) { + ErrorHandlerData *data = (ErrorHandlerData *)p_this; + GDScriptTest *self = data->self; + TestResult *result = data->result; + + result->status = GDTEST_RUNTIME_ERROR; + + StringBuilder builder; + builder.append(">> "); + switch (p_type) { + case ERR_HANDLER_ERROR: + builder.append("ERROR"); + break; + case ERR_HANDLER_WARNING: + builder.append("WARNING"); + break; + case ERR_HANDLER_SCRIPT: + builder.append("SCRIPT ERROR"); + break; + case ERR_HANDLER_SHADER: + builder.append("SHADER ERROR"); + break; + default: + builder.append("Unknown error type"); + break; + } + + builder.append("\n>> "); + builder.append(p_function); + builder.append("\n>> "); + builder.append(p_function); + builder.append("\n>> "); + builder.append(String(p_file).trim_prefix(self->base_dir)); + builder.append("\n>> "); + builder.append(itos(p_line)); + builder.append("\n>> "); + builder.append(p_error); + if (strlen(p_explanation) > 0) { + builder.append("\n>> "); + builder.append(p_explanation); + } + builder.append("\n"); + + result->output = builder.as_string(); +} + +bool GDScriptTest::check_output(const String &p_output) const { + Error err = OK; + String expected = FileAccess::get_file_as_string(output_file, &err); + + ERR_FAIL_COND_V_MSG(err != OK, false, "Error when opening the output file."); + + String got = p_output.strip_edges(); // TODO: may be hacky. + got += "\n"; // Make sure to insert newline for CI static checks. + + return got == expected; +} + +String GDScriptTest::get_text_for_status(GDScriptTest::TestStatus p_status) const { + switch (p_status) { + case GDTEST_OK: + return "GDTEST_OK"; + case GDTEST_LOAD_ERROR: + return "GDTEST_LOAD_ERROR"; + case GDTEST_PARSER_ERROR: + return "GDTEST_PARSER_ERROR"; + case GDTEST_ANALYZER_ERROR: + return "GDTEST_ANALYZER_ERROR"; + case GDTEST_COMPILER_ERROR: + return "GDTEST_COMPILER_ERROR"; + case GDTEST_RUNTIME_ERROR: + return "GDTEST_RUNTIME_ERROR"; + } + return ""; +} + +GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { + disable_stdout(); + + TestResult result; + result.status = GDTEST_OK; + result.output = String(); + + Error err = OK; + + // Create script. + Ref<GDScript> script; + script.instance(); + script->set_path(source_file); + script->set_script_path(source_file); + err = script->load_source_code(source_file); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not load source code for: '" + source_file + "'"); + } + + // Test parsing. + GDScriptParser parser; + err = parser.parse(script->get_source_code(), source_file, false); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_PARSER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) { + result.output += E->get().message + "\n"; // TODO: line, column? + break; // Only the first error since the following might be cascading. + } + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + // Test type-checking. + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_ANALYZER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) { + result.output += E->get().message + "\n"; // TODO: line, column? + break; // Only the first error since the following might be cascading. + } + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + StringBuilder warning_string; + for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E != nullptr; E = E->next()) { + const GDScriptWarning warning = E->get(); + warning_string.append(">> WARNING"); + warning_string.append("\n>> Line: "); + warning_string.append(itos(warning.start_line)); + warning_string.append("\n>> "); + warning_string.append(warning.get_name()); + warning_string.append("\n>> "); + warning_string.append(warning.get_message()); + warning_string.append("\n"); + } + result.output += warning_string.as_string(); + + // Test compiling. + GDScriptCompiler compiler; + err = compiler.compile(&parser, script.ptr(), false); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_COMPILER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + result.output = compiler.get_error(); + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + // Test running. + const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name); + if (test_function_element == nullptr) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.output = ""; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'"); + } + + script->reload(); + + // Create object instance for test. + Object *obj = ClassDB::instance(script->get_native()->get_name()); + Ref<Reference> obj_ref; + if (obj->is_reference()) { + obj_ref = Ref<Reference>(Object::cast_to<Reference>(obj)); + } + obj->set_script(script); + GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance()); + + // Setup output handlers. + ErrorHandlerData error_data(&result, this); + + _print_handler.userdata = &result; + _error_handler.userdata = &error_data; + add_print_handler(&_print_handler); + add_error_handler(&_error_handler); + + // Call test function. + Callable::CallError call_err; + instance->call(GDScriptTestRunner::test_function_name, nullptr, 0, call_err); + + // Tear down output handlers. + remove_print_handler(&_print_handler); + remove_error_handler(&_error_handler); + + // Check results. + if (call_err.error != Callable::CallError::CALL_OK) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not call test function on: '" + source_file + "'"); + } + + result.output = get_text_for_status(result.status) + "\n" + result.output; + if (!p_is_generating) { + result.passed = check_output(result.output); + } + + if (obj_ref.is_null()) { + memdelete(obj); + } + + enable_stdout(); + return result; +} + +GDScriptTest::TestResult GDScriptTest::run_test() { + return execute_test_code(false); +} + +bool GDScriptTest::generate_output() { + TestResult result = execute_test_code(true); + if (result.status == GDTEST_LOAD_ERROR) { + return false; + } + + Error err = OK; + FileAccessRef out_file = FileAccess::open(output_file, FileAccess::WRITE, &err); + if (err != OK) { + return false; + } + + String output = result.output.strip_edges(); // TODO: may be hacky. + output += "\n"; // Make sure to insert newline for CI static checks. + + out_file->store_string(output); + out_file->close(); + + return true; +} + +} // namespace GDScriptTests diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h new file mode 100644 index 0000000000..9b2d14a371 --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* gdscript_test_runner.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_TEST_H +#define GDSCRIPT_TEST_H + +#include "../gdscript.h" +#include "core/error/error_macros.h" +#include "core/string/print_string.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" + +namespace GDScriptTests { + +void init_autoloads(); +void init_language(const String &p_base_path); +void finish_language(); + +// Single test instance in a suite. +class GDScriptTest { +public: + enum TestStatus { + GDTEST_OK, + GDTEST_LOAD_ERROR, + GDTEST_PARSER_ERROR, + GDTEST_ANALYZER_ERROR, + GDTEST_COMPILER_ERROR, + GDTEST_RUNTIME_ERROR, + }; + + struct TestResult { + TestStatus status; + String output; + bool passed; + }; + +private: + struct ErrorHandlerData { + TestResult *result; + GDScriptTest *self; + ErrorHandlerData(TestResult *p_result, GDScriptTest *p_this) { + result = p_result; + self = p_this; + } + }; + + String source_file; + String output_file; + String base_dir; + + PrintHandlerList _print_handler; + ErrorHandlerList _error_handler; + + void enable_stdout(); + void disable_stdout(); + bool check_output(const String &p_output) const; + String get_text_for_status(TestStatus p_status) const; + + TestResult execute_test_code(bool p_is_generating); + +public: + static void print_handler(void *p_this, const String &p_message, bool p_error); + static void error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type); + TestResult run_test(); + bool generate_output(); + + const String &get_source_file() const { return source_file; } + const String &get_output_file() const { return output_file; } + + GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir); + GDScriptTest() : + GDScriptTest(String(), String(), String()) {} // Needed to use in Vector. +}; + +class GDScriptTestRunner { + String source_dir; + Vector<GDScriptTest> tests; + + bool is_generating = false; + bool do_init_languages = false; + + bool make_tests(); + bool make_tests_for_dir(const String &p_dir); + bool generate_class_index(); + +public: + static StringName test_function_name; + + static void handle_cmdline(); + int run_tests(); + bool generate_outputs(); + + GDScriptTestRunner(const String &p_source_dir, bool p_init_language); + ~GDScriptTestRunner(); +}; + +} // namespace GDScriptTests + +#endif // GDSCRIPT_TEST_H diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h new file mode 100644 index 0000000000..136907b316 --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* gdscript_test_runner_suite.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_TEST_RUNNER_SUITE_H +#define GDSCRIPT_TEST_RUNNER_SUITE_H + +#include "gdscript_test_runner.h" +#include "tests/test_macros.h" + +namespace GDScriptTests { + +TEST_SUITE("[Modules][GDScript]") { + // GDScript 2.0 is still under heavy construction. + // Allow the tests to fail, but do not ignore errors during development. + // Update the scripts and expected output as needed. + TEST_CASE("Script compilation and runtime") { + GDScriptTestRunner runner("modules/gdscript/tests/scripts", true); + int fail_count = runner.run_tests(); + INFO("Make sure `*.out` files have expected results."); + REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass."); + } +} + +} // namespace GDScriptTests + +#endif // GDSCRIPT_TEST_RUNNER_SUITE_H diff --git a/modules/gdscript/tests/scripts/.gitignore b/modules/gdscript/tests/scripts/.gitignore new file mode 100644 index 0000000000..94c5b1bf6b --- /dev/null +++ b/modules/gdscript/tests/scripts/.gitignore @@ -0,0 +1,2 @@ +# Ignore metadata if someone open this on Godot. +/.godot diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd new file mode 100644 index 0000000000..c56ad94095 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out b/modules/gdscript/tests/scripts/parser/errors/missing_argument.out new file mode 100644 index 0000000000..fc2a891109 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_argument.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Too few arguments for "args()" call. Expected at least 2 but received 1. diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd new file mode 100644 index 0000000000..a1077e1985 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd @@ -0,0 +1,2 @@ +func test(): + var a = ("missing paren ->" diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out new file mode 100644 index 0000000000..7326afa33d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected closing ")" after grouping expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd new file mode 100644 index 0000000000..62cb633e9e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd @@ -0,0 +1,3 @@ +func test(): + if true # Missing colon here. + print("true") diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.out b/modules/gdscript/tests/scripts/parser/errors/missing_colon.out new file mode 100644 index 0000000000..687b963bc8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected ":" after "if" condition. diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd new file mode 100644 index 0000000000..116b0151da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,2 diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out new file mode 100644 index 0000000000..34ea7ac323 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected closing ")" after call arguments. diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd new file mode 100644 index 0000000000..9ad77f1432 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd @@ -0,0 +1,3 @@ +func test(): + print("Using spaces") + print("Using tabs") diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out new file mode 100644 index 0000000000..6390de9788 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Used "\t" for indentation instead " " as used before in the file. diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd new file mode 100644 index 0000000000..3875ce3936 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + var a = $ # Expected some node path. diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd new file mode 100644 index 0000000000..6fd2692d47 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + $23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd new file mode 100644 index 0000000000..1836d42226 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + $MyNode/23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out new file mode 100644 index 0000000000..dcb4ccecb0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path after "/". diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd new file mode 100644 index 0000000000..08f2eedb2d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd @@ -0,0 +1,2 @@ +func test(): + print("A"); print("B") diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out new file mode 100644 index 0000000000..fc03f3efe8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out @@ -0,0 +1,3 @@ +GDTEST_OK +A +B diff --git a/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd new file mode 100644 index 0000000000..6097b11b10 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd @@ -0,0 +1,7 @@ +# See https://github.com/godotengine/godot/issues/41066. + +func f(p, ): ## <-- no errors + print(p) + +func test(): + f(0, ) ## <-- no error diff --git a/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out new file mode 100644 index 0000000000..94e2ec2af8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out @@ -0,0 +1,2 @@ +GDTEST_OK +0 diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd new file mode 100644 index 0000000000..3b48f10ca7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd @@ -0,0 +1,12 @@ +var a # No init. +var b = 42 # Init. + +func test(): + var c # No init, local. + var d = 23 # Init, local. + + a = 1 + c = 2 + + prints(a, b, c, d) + print("OK") diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out new file mode 100644 index 0000000000..2e0a63c024 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out @@ -0,0 +1,7 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> UNASSIGNED_VARIABLE +>> The variable 'c' was used but never assigned a value. +1 42 2 23 +OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd new file mode 100644 index 0000000000..68e3bd424f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd @@ -0,0 +1,2 @@ +func test(): + var unused = "not used" diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out new file mode 100644 index 0000000000..270e0e69c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_VARIABLE +>> The local variable 'unused' is declared but never used in the block. If this is intended, prefix it with an underscore: '_unused' diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot new file mode 100644 index 0000000000..25b49c0abd --- /dev/null +++ b/modules/gdscript/tests/scripts/project.godot @@ -0,0 +1,10 @@ +; This is not an actual project. +; This config only exists to properly set up the test environment. +; It also helps for opening Godot to edit the scripts, but please don't +; let the editor changes be saved. + +config_version=4 + +[application] + +config/name="GDScript Integration Test Suite" diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp new file mode 100644 index 0000000000..36da64bbaa --- /dev/null +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -0,0 +1,251 @@ +/*************************************************************************/ +/* 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 GDScriptTests { + +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 <= p_lines.size(); 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)); + } + } + + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); + + 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)); + } + } + +#ifdef TOOLS_ENABLED + GDScriptParser::TreePrinter printer; + printer.print_tree(parser); +#endif +} + +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 + ")"); +#ifdef TOOLS_ENABLED + func->disassemble(p_lines); +#endif + print_line(""); + print_line(""); + } +} + +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); + + // Initialize the language for the test routine. + init_language(fa->get_path_absolute().get_base_dir()); + + 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."); + } + + finish_language(); +} +} // namespace GDScriptTests diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h new file mode 100644 index 0000000000..c7ee5a2208 --- /dev/null +++ b/modules/gdscript/tests/test_gdscript.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* 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 + +#include "gdscript_test_runner.h" +#include "tests/test_macros.h" + +namespace GDScriptTests { + +enum TestType { + TEST_TOKENIZER, + TEST_PARSER, + TEST_COMPILER, + TEST_BYTECODE, +}; + +void test(TestType p_type); + +} // namespace GDScriptTests + +#endif // TEST_GDSCRIPT_H |