feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

View file

@ -0,0 +1,8 @@
[*.gd]
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.out]
insert_final_newline = true

View file

@ -0,0 +1,139 @@
# Basic GDScript module architecture
This provides some basic information in how GDScript is implemented and integrates with the rest of the engine. You can learn more about GDScript in the [documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/index.html). It describes the syntax and user facing systems and concepts, and can be used as a reference for what user expectations are.
## General design
GDScript is:
1. A [gradually typed](https://en.wikipedia.org/wiki/Gradual_typing) language. Type hints are optional and help with static analysis and performance. However, typed code must easily interoperate with untyped code.
2. A tightly designed language. Features are added because they are _needed_, and not because they can be added or are interesting to develop.
3. Primarily an interpreted scripting language: it is compiled to GDScript byte code and interpreted in a GDScript virtual machine. It is meant to be easy to use and develop gameplay in. It is not meant for CPU-intensive algorithms or data processing, and is not optimized for it. For that, [C#](https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_basics.html) or [GDExtension](https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/what_is_gdextension.html) may be used.
## Integration into Godot
GDScript is integrated into Godot as a module. Since modules are optional, this means that Godot may be built without GDScript and work perfectly fine without it!
The GDScript module interfaces with Godot's codebase by inheriting from the engine's scripting-related classes. New languages inherit from [`ScriptLanguage`](/core/object/script_language.h), and are registered in Godot's [`ScriptServer`](/core/object/script_language.h). Scripts, referring to a file containing code, are represented in the engine by the `Script` class. Instances of that script, which are used at runtime when actually executing the code, inherit from [`ScriptInstance`](/core/object/script_instance.h).
To access Godot's internal classes, GDScript uses [`ClassDB`](/core/object/class_db.h). `ClassDB` is where Godot registers classes, methods and properties that it wants exposed to its scripting system. This is how GDScript understands that `Node2D` is a class it can use, and that it has a `get_parent()` method.
[Built-in GDScript methods](https://docs.godotengine.org/en/latest/classes/class_@gdscript.html#methods) are defined and exported by [`GDScriptUtilityFunctions`](gdscript_utility_functions.h), whereas [global scope methods](https://docs.godotengine.org/en/latest/classes/class_%2540globalscope.html) are registered in [`Variant::_register_variant_utility_functions()`](/core/variant/variant_utility.cpp).
## Compilation
Scripts can be at different stages of compilation. The process isn't entirely linear, but consists of this general order: tokenizing, parsing, analyzing, and finally compiling. This process is the same for scripts in the editor and scripts in an exported game. Scripts are stored as text files in both cases, and the compilation process must happen in full before the bytecode can be passed to the virtual machine and run.
The main class of the GDScript module is the [`GDScript`](gdscript.h) class, which represents a class defined in GDScript. Each `.gd` file is called a _class file_ because it implicitly defines a class in GDScript, and thus results in an associated `GDScript` object. However, GDScript classes may define [_inner classes_](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#inner-classes), and those are also represented by further `GDScript` objects, even though they are not in files of their own.
The `GDScript` class contains all the information related to the corresponding GDScript class: its name and path, its members like variables, functions, symbols, signals, implicit methods like initializers, etc. This is the main class that the compilation step deals with.
A secondary class is `GDScriptInstance`, defined in the same file, containing _runtime_ information for an instance of a `GDScript`, and is more related to the execution of a script by the virtual machine.
### Loading source code
This mostly happens by calling `GDScript::load_source_code()` on a `GDScript` object. Parsing only requires a `String`, so it is entirely possible to parse a script without a `GDScript` object!
### Tokenizing (see [`GDScriptTokenizer`](gdscript_tokenizer.h))
Tokenizing is the process of converting the source code `String` into a sequence of tokens, which represent language constructs (such as `for` or `if`), identifiers, literals, etc. This happens almost exclusively during the parsing process, which asks for the next token in order to make sense of the source code. The tokenizer is only used outside of the parsing process in very rare exceptions.
### Parsing (see [`GDScriptParser`](gdscript_parser.h))
The parser takes a sequence of tokens and builds [the abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of the GDScript program. The AST is used in the analyzing and compilation steps, and the source code `String` and sequence of tokens are discarded. The AST-building process finds syntax errors in a GDScript program and reports them to the user.
The parser class also defines all the possible nodes of the AST as subtypes of `GDScriptParser::Node`, not to be confused with Godot's scene tree `Node`. For example, `GDScriptParser::IfNode` has two children nodes, one for the code in the `if` block, and one for the code in the `else` block. A `GDScriptParser::FunctionNode` contains children nodes for its name, parameters, return type, body, etc. The parser also defines typechecking data structures like `GDScriptParser::Datatype`.
The parser was [intentionally designed](https://godotengine.org/article/gdscript-progress-report-writing-new-parser/#less-lookahead) with a look-ahead of a single token. This means that the parser only has access to the current token and the previous token (or, if you prefer, the current token and the next token). This parsing limitation ensures that GDScript will remain syntactically simple and accessible, and that the parsing process cannot become overly complex.
### Analysis and typechecking (see [`GDScriptAnalyzer`](gdscript_analyzer.h))
The analyzer takes in the AST of a program and verifies that "everything checks out". For example, when analyzing a method call with three parameters, it will check whether the function definition also contains three parameters. If the code is typed, it will check that argument and parameter types are compatible.
There are two types of functions in the analyzer: `reduce` functions and `resolve` functions. Their parameters always include the AST node that they are attempting to reduce or resolve.
- The `reduce` functions work on GDScript expressions, which return values, and thus their main goal is to populate the `GDScriptParser::Datatype` of the underlying AST node. The datatype is then used to typecheck code that depends on this expression, and gives the compiler necessary information to generate appropriate, safe, and optimized bytecode.
For example, function calls are handled with `reduce_call()`, which must figure out what function is being called and check that the passed arguments match the function's parameters. The type of the underlying `CallNode` will be the return type of the function.
Another example is `reduce_identifier()`, which does _a lot_ of work: given the string of its `IdentifierNode`, it must figure out what that identifier refers to. It could be a local variable, class name, global or class function, function parameter, class or superclass member, or any number of other things. It has to check many different places to find this information!
A secondary goal of the `reduce` functions is to perform [constant folding](https://en.wikipedia.org/wiki/Constant_folding): to determine whether an expression is constant, and if it is, compute its _reduced value_ at this time so it does not need to be computed over and over at runtime!
- The resolve functions work on AST nodes that represent statements, and don't necessarily have values. Their goal is to do work related to program control flow, resolve their child AST nodes, deal with scoping, etc. One of the simplest examples is `resolve_if()`, which reduces the `if` condition, then resolves the `if` body and `else` body if it exists.
The `resolve_for()` function does more work than simply resolving its code block. With `for i in range(10)`, for example, it must also declare and type the new variable `i` within the scope of its code block, as well as make sure `range(10)` is iterable, among other things.
To understand classes and inheritance without introducing cyclic dependency problems that would come from immediate full class code analysis, the analyzer often asks only for class _interfaces_: it needs to know what member variables and methods exist as well as their types, but no more.
This is done through `resolve_class_interface()`, which populates `ClassNode`'s `Datatype` with that information. It first checks for superclass information with `resolve_class_inheritance()`, then populates its member information by calling `resolve_class_member()` on each member. Since this step is only about the class _interface_, methods are resolved with `resolve_function_signature()`, which gets all relevant typing information without resolving the function body!
The remaining steps of resolution, including member variable initialization code, method code, etc, can happen at a later time.
In fully untyped code, very little static analysis is possible. For example, the analyzer cannot know whether `my_var.some_member` exists when it does not know the type of `my_var`. Therefore, it cannot emit a warning or error because `some_member` _could_ exist - or it could not. The analyzer must trust the programmer. If an error does occur, it will be at runtime.
However, GDScript is gradually typed, so all of these analyses must work when parts of the code are typed and others untyped. Static analysis in a gradually typed language is a best-effort situation: suppose there is a typed variable `var x : int`, and an untyped `var y = "some string"`. We can obviously tell this isn't going to work, but the analyzer will accept the assignment `x = y` without warnings or errors: it only knows that `y` is untyped and can therefore be anything, including the `int` that `x` expects. It must once again trust the programmer to have written code that works. In this instance, the code will error at runtime.
In both these cases, the analyzer handles the uncertainty of untyped code by calling `mark_node_unsafe()` on the respective AST node. This means it didn't have enough information to know whether the code was fully safe or necessarily wrong. Lines with unsafe AST nodes are represented by gray line numbers in the GDScript editor. Green line numbers indicate a line of code without any unsafe nodes.
This analysis step is also where dependencies are introduced and that information stored for use later. If class `A` extends class `B` or contains a member with type `B` from some other script file, then the analyzer will attempt to load that second script. If `B` contains references to `A`, then a _cyclic_ dependency is introduced. This is OK in many cases, but impossible to resolve in others.
Clearly, the analyzer is where a lot of the "magic" happens! It determines what constitutes proper code that can actually be compiled, and provides as many safety guarantees as possible with the typing information it is provided with. The more typed the code, the safer and more optimized it will be!
#### Cyclic dependencies and member resolution
Cyclic dependencies from inheritance (`A extends B, B extends A`) are not supported in any programming language. Other cyclic dependencies are supported, such as `A extends B` and `B` uses, contains, or preloads, members of type `A`.
To see why cyclic dependencies are complicated, suppose there is one between classes `A <-> B`. Partially through the analysis of `A`, we will need information about `B`, and therefore trigger its analysis. However, the analysis of `B` will eventually need information from `A`, which is incomplete because we never finished analyzing it. This would result in members not being found when they actually exist!
GDScript supports cyclic dependencies due to a few features of the analyzer:
1. Class interface resolution: when analyzing code of class `A` that depends on some other class `B`, we don't need to resolve the _code_ of `B` (its member initializers, function code, etc). We only need to know what members and methods the class has, as well as their types. These are the only things one class can use to work with, or _interface_ with, another. Because of inheritance, a class's interface depends on its superclass as well, so recursive interface resolution is needed. More details can be found in `GDScriptAnalyzer::resolve_class_interface()`.
2. Out of order member resolution: the analyzer may not even need an entire class interface to be resolved in order to figure out a specific type! For example, if class `A` contains code that references `B.is_alive`, then the analyzer doesn't need to immediately resolve `B`'s entire interface. It may simply check whether `is_alive` exists in `B`, and reduce it for its type information, on-demand.
A fundamental cyclic dependency problem occurs when the types of two different member variables are mutually dependent. This is commonly checked by a pattern that declares a temporary datatype with `GDScriptParser::DataType resolving_datatype;`, followed by `resolving_datatype.kind = GDScriptParser::DataType::RESOLVING;`. If the analyzer attempts to resolve a member on-demand that is already tagged as resolving, then a cyclic dependency problem has been found and can be reported.
### Compiling (see [`GDScriptCompiler`](gdscript_compiler.h))
Compiling is the final step in making a GDScript executable in the [virtual machine](gdscript_vm.h) (VM). The compiler takes a `GDScript` object and an AST, and uses another class, [`GDScriptByteCodeGenerator`](gdscript_byte_codegen.h), to generate bytecode corresponding to the class. In doing this, it creates the objects that the VM understands how to run, like [`GDScriptFunction`](gdscript_function.h), and completes a few extra tasks needed for compilation, such as populating runtime class member information.
Importantly, the compilation process of a class, specifically the `GDScriptCompiler::_compile_class()` method, _cannot_ depend on information obtained by calling `GDScriptCompiler::_compile_class()` on another class, for the same cyclic dependency reasons explained in the previous section.
Any information that can only be obtained or populated during the compilation step, when `GDScript` objects become available, must be handled before `GDScriptCompiler::_compile_class()` is called. This process is centralized in `GDScriptCompiler::_prepare_compilation()` which works as the compile-time equivalent of `GDScriptAnalyzer::resolve_class_interface()`: it populates a `GDScript`'s "interface" exclusively with information from the analysis step, and without processing other external classes. This information may then be referenced by other classes without introducing problematic cycles.
The more typing information a GDScript has, the more optimized the compiled bytecode can be. For example, if `my_var` is untyped, the bytecode for `my_var.some_member` will need to go through several layers of indirection to figure out the type of `my_var` at runtime, and from there determine how to obtain `some_member`. This varies depending on whether `my_var` is a dictionary, a script, or a native class. If the type of `my_var` was known at compile time, the bytecode can directly call the type-specific method for obtaining a member.
Similar optimizations are possible for `my_var.some_func()`. With untyped GDScript, the VM will need to resolve `my_var`'s type at runtime, then, depending on the type, use different methods to resolve the function and call it. When the function is fully resolved during static analysis, native function pointers or GDScript function objects can be compiled into the bytecode and directly called by the VM, removing several layers of indirection.
Typed code is safer code and faster code!
## Loading scripts
GDScripts can be loaded in a couple of different ways. The main method, used almost everywhere in the engine, is to load scripts through the `ResourceLoader` singleton. In this way, GDScripts are resources like any others: `ResourceLoader::load()` will simply reroute to `ResourceFormatLoaderGDScript::load()`, found in `gdscript.h/cpp`(gdscript.h). This generates a GDScript object which is compiled and ready to use.
The other method is to manually load the source code, then pass it to a parser, then to an analyzer and then to a compiler. The previous approach does this behind the scenes, alongside some smart caching of scripts and other functionalities. It is used in the [GDScript test runner infrastructure](tests/gdscript_test_runner.h).
### Full and shallow scripts
The `ResourceFormatLoaderGDScript::load()` method simply calls `GDScriptCache::get_full_script()`. The [`GDScriptCache`](gdscript_cache.h) is, as it sounds, a cache for GDScripts. Its two main methods, `get_shallow_script()` and `get_full_script()`, get and cache, respectively, scripts that have been merely parsed, and scripts which have been statically analyzed and fully compiled. Another internal class, `GDScriptParserRef`, found in the same file, provides even more granularity over the different steps of the parsing process, and is used extensively in the analyzer.
Shallow, or "just parsed" scripts, provide information such as defined classes, class members, and so forth. This is sufficient for many purposes, like obtaining a class interface or checking whether a member exists on a specific class. Full scripts, on the other hand, have been analyzed and compiled and are ready to use.
The distinction between full and shallow scripts is very important, as shallow scripts cannot create cyclic dependency problems, whereas full scripts can. The analyzer, for example, never asks for full scripts. Choosing when to request a shallow vs a full script is an important but subtle decision.
In practice, full scripts are simply scripts where `GDScript::reload()` has been called. This critical function is the primary way in which scripts get compiled in Godot, and essentially does all the compilation steps covered so far in order. Whenever a script is loaded, or updated and reloaded in Godot, it will end up going through `GDScript::reload()`, except in very rare circumstances like the test runner. It is an excellent place to start reading and understanding the GDScript module!
## Special types of scripts
Certain types of GDScripts behave slightly differently. For example, autoloads are loaded with `ResourceLoader::load()` during `Main::start()`, very soon after Godot is launched. Many systems aren't initialized at that time, so error reporting is often significantly reduced and may not even show up in the editor.
Tool scripts, declared with the `@tool` annotation on a GDScript file, run in the editor itself as opposed to just when the game is launched. This leads to a significant increase in complexity, as many things that can be changed in the editor may affect a currently executing tool script.
## Other
There are many other classes in the GDScript module. Here is a brief overview of some of them:
- Declaration of GDScript warnings in [`GDScriptWarning`](gdscript_warning.h).
- [`GDScriptFunction`](gdscript_function.h), which represents an executable GDScript function. The relevant file contains both static as well as runtime information.
- The [virtual machine](gdscript_vm.cpp) is essentially defined as calling `GDScriptFunction::call()`.
- Editor-related functions can be found in parts of `GDScriptLanguage`, originally declared in [`gdscript.h`](gdscript.h) but defined in [`gdscript_editor.cpp`](gdscript_editor.cpp). Code highlighting can be found in [`GDScriptSyntaxHighlighter`](editor/gdscript_highlighter.h).
- GDScript decompilation is found in [`gdscript_disassembler.cpp`](gdscript_disassembler.h), defined as `GDScriptFunction::disassemble()`.
- Documentation generation from GDScript comments in [`GDScriptDocGen`](editor/gdscript_docgen.h)

View file

@ -0,0 +1,28 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_gdscript = env_modules.Clone()
env_gdscript.add_source_files(env.modules_sources, "*.cpp")
if env.editor_build:
env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp")
SConscript("editor/script_templates/SCsub")
# Those two modules are required for the language server protocol
if env["module_jsonrpc_enabled"] and env["module_websocket_enabled"]:
env_gdscript.add_source_files(env.modules_sources, "./language_server/*.cpp")
else:
# 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"])
# Also needed in main env to unexpose --lsp-port option.
env.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"])
if env["tests"]:
env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"])
env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp")

View file

@ -0,0 +1,18 @@
def can_build(env, platform):
env.module_add_dependencies("gdscript", ["jsonrpc", "websocket"], True)
return True
def configure(env):
pass
def get_doc_classes():
return [
"@GDScript",
"GDScript",
]
def get_doc_path():
return "doc_classes"

View file

@ -0,0 +1,753 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="@GDScript" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
A list of GDScript-specific utility functions and annotations accessible from any script.
For the list of the global functions and constants see [@GlobalScope].
</description>
<tutorials>
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
</tutorials>
<methods>
<method name="Color8">
<return type="Color" />
<param index="0" name="r8" type="int" />
<param index="1" name="g8" type="int" />
<param index="2" name="b8" type="int" />
<param index="3" name="a8" type="int" default="255" />
<description>
Returns a [Color] constructed from red ([param r8]), green ([param g8]), blue ([param b8]), and optionally alpha ([param a8]) integer channels, each divided by [code]255.0[/code] for their final value. Using [method Color8] instead of the standard [Color] constructor is useful when you need to match exact color values in an [Image].
[codeblock]
var red = Color8(255, 0, 0) # Same as Color(1, 0, 0).
var dark_blue = Color8(0, 0, 51) # Same as Color(0, 0, 0.2).
var my_color = Color8(306, 255, 0, 102) # Same as Color(1.2, 1, 0, 0.4).
[/codeblock]
[b]Note:[/b] Due to the lower precision of [method Color8] compared to the standard [Color] constructor, a color created with [method Color8] will generally not be equal to the same color created with the standard [Color] constructor. Use [method Color.is_equal_approx] for comparisons to avoid issues with floating-point precision error.
</description>
</method>
<method name="assert">
<return type="void" />
<param index="0" name="condition" type="bool" />
<param index="1" name="message" type="String" default="&quot;&quot;" />
<description>
Asserts that the [param condition] is [code]true[/code]. If the [param condition] 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.
An optional [param message] can be shown in addition to the generic "Assertion failed" message. You can use this to provide additional details about why the assertion failed.
[b]Warning:[/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.
[codeblock]
# Imagine we always want speed to be between 0 and 20.
var speed = -10
assert(speed &lt; 20) # True, the program will continue.
assert(speed &gt;= 0) # False, the program will stop.
assert(speed &gt;= 0 and speed &lt; 20) # You can also combine the two conditional statements in one check.
assert(speed &lt; 20, "the speed limit is 20") # Show a message.
[/codeblock]
[b]Note:[/b] [method assert] is a keyword, not a function. So you cannot access it as a [Callable] or use it inside expressions.
</description>
</method>
<method name="char">
<return type="String" />
<param index="0" name="char" type="int" />
<description>
Returns a single character (as a [String]) of the given Unicode code point (which is compatible with ASCII code).
[codeblock]
a = char(65) # a is "A"
a = char(65 + 32) # a is "a"
a = char(8364) # a is "€"
[/codeblock]
</description>
</method>
<method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
<param index="1" name="type" type="int" />
<description>
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
var a = [4, 2.5, 1.2]
print(a is Array) # Prints true
var b = convert(a, TYPE_PACKED_BYTE_ARRAY)
print(b) # Prints [4, 2, 1]
print(b is Array) # Prints false
[/codeblock]
</description>
</method>
<method name="dict_to_inst">
<return type="Object" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
Converts a [param dictionary] (created with [method inst_to_dict]) back to an Object instance. Can be useful for deserializing.
</description>
</method>
<method name="get_stack">
<return type="Array" />
<description>
Returns an array of dictionaries representing the current call stack. See also [method print_stack].
[codeblock]
func _ready():
foo()
func foo():
bar()
func bar():
print(get_stack())
[/codeblock]
Starting from [code]_ready()[/code], [code]bar()[/code] would print:
[codeblock lang=text]
[{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}]
[/codeblock]
[b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method get_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
[b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will return an empty array.
</description>
</method>
<method name="inst_to_dict">
<return type="Dictionary" />
<param index="0" name="instance" type="Object" />
<description>
Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing.
[b]Note:[/b] Cannot be used to serialize objects with built-in scripts attached or objects allocated within built-in scripts.
[codeblock]
var foo = "bar"
func _ready():
var d = inst_to_dict(self)
print(d.keys())
print(d.values())
[/codeblock]
Prints out:
[codeblock lang=text]
[@subpath, @path, foo]
[, res://test.gd, bar]
[/codeblock]
</description>
</method>
<method name="is_instance_of">
<return type="bool" />
<param index="0" name="value" type="Variant" />
<param index="1" name="type" type="Variant" />
<description>
Returns [code]true[/code] if [param value] is an instance of [param type]. The [param type] value must be one of the following:
- A constant from the [enum Variant.Type] enumeration, for example [constant TYPE_INT].
- An [Object]-derived class which exists in [ClassDB], for example [Node].
- A [Script] (you can use any class, including inner one).
Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays). Use the operator instead of this method if you do not need dynamic type checking.
Examples:
[codeblock]
print(is_instance_of(a, TYPE_INT))
print(is_instance_of(a, Node))
print(is_instance_of(a, MyClass))
print(is_instance_of(a, MyClass.InnerClass))
[/codeblock]
[b]Note:[/b] If [param value] and/or [param type] are freed objects (see [method @GlobalScope.is_instance_valid]), or [param type] is not one of the above options, this method will raise a runtime error.
See also [method @GlobalScope.typeof], [method type_exists], [method Array.is_same_typed] (and other [Array] methods).
</description>
</method>
<method name="len">
<return type="int" />
<param index="0" name="var" type="Variant" />
<description>
Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], the element count of any array type, or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped.
[codeblock]
a = [1, 2, 3, 4]
len(a) # Returns 4
b = "Hello!"
len(b) # Returns 6
[/codeblock]
</description>
</method>
<method name="load">
<return type="Resource" />
<param index="0" name="path" type="String" />
<description>
Returns a [Resource] from the filesystem located at the absolute [param path]. Unless it's already referenced elsewhere (such as in another script or in the scene), the resource is loaded from disk on function call, which might cause a slight delay, especially when loading large scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload]. This method is equivalent of using [method ResourceLoader.load] with [constant ResourceLoader.CACHE_MODE_REUSE].
[b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path", or by dragging the file from the FileSystem dock into the current script.
[codeblock]
# Load a scene called "main" located in the root of the project directory and cache it in a variable.
var main = load("res://main.tscn") # main will contain a PackedScene resource.
[/codeblock]
[b]Important:[/b] Relative paths are [i]not[/i] relative to the script calling this method, instead it is prefixed with [code]"res://"[/code]. Loading from relative paths might not work as expected.
This function is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios.
[b]Note:[/b] Files have to be imported into the engine first to load them using this function. If you want to load [Image]s at run-time, you may use [method Image.load]. If you want to import audio files, you can use the snippet described in [member AudioStreamMP3.data].
[b]Note:[/b] If [member ProjectSettings.editor/export/convert_text_resources_to_binary] is [code]true[/code], [method @GDScript.load] will not be able to read converted files in an exported project. If you rely on run-time loading of files present within the PCK, set [member ProjectSettings.editor/export/convert_text_resources_to_binary] to [code]false[/code].
</description>
</method>
<method name="preload">
<return type="Resource" />
<param index="0" name="path" type="String" />
<description>
Returns a [Resource] from the filesystem located at [param path]. During run-time, the resource is loaded when the script is being parsed. This function effectively acts as a reference to that resource. Note that this function requires [param path] to be a constant [String]. If you want to load a resource from a dynamic/variable path, use [method load].
[b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the Assets Panel and choosing "Copy Path", or by dragging the file from the FileSystem dock into the current script.
[codeblock]
# Create instance of a scene.
var diamond = preload("res://diamond.tscn").instantiate()
[/codeblock]
[b]Note:[/b] [method preload] is a keyword, not a function. So you cannot access it as a [Callable].
</description>
</method>
<method name="print_debug" qualifiers="vararg">
<return type="void" />
<description>
Like [method @GlobalScope.print], but includes the current stack frame when running with the debugger turned on.
The output in the console may look like the following:
[codeblock lang=text]
Test print
At: res://test.gd:15:_process()
[/codeblock]
[b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID.
</description>
</method>
<method name="print_stack">
<return type="void" />
<description>
Prints a stack trace at the current code location. See also [method get_stack].
The output in the console may look like the following:
[codeblock lang=text]
Frame 0 - res://test.gd:16 in function '_process'
[/codeblock]
[b]Note:[/b] This function only works if the running instance is connected to a debugging server (i.e. an editor instance). [method print_stack] will not work in projects exported in release mode, or in projects exported in debug mode if not connected to a debugging server.
[b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will instead print the thread ID.
</description>
</method>
<method name="range" qualifiers="vararg" keywords="seq">
<return type="Array" />
<description>
Returns an array with the given range. [method range] can be called in three ways:
[code]range(n: int)[/code]: Starts from 0, increases by steps of 1, and stops [i]before[/i] [code]n[/code]. The argument [code]n[/code] is [b]exclusive[/b].
[code]range(b: int, n: int)[/code]: Starts from [code]b[/code], increases by steps of 1, and stops [i]before[/i] [code]n[/code]. The arguments [code]b[/code] and [code]n[/code] are [b]inclusive[/b] and [b]exclusive[/b], respectively.
[code]range(b: int, n: int, s: int)[/code]: Starts from [code]b[/code], increases/decreases by steps of [code]s[/code], and stops [i]before[/i] [code]n[/code]. The arguments [code]b[/code] and [code]n[/code] are [b]inclusive[/b] and [b]exclusive[/b], respectively. The argument [code]s[/code] [b]can[/b] be negative, but not [code]0[/code]. If [code]s[/code] is [code]0[/code], an error message is printed.
[method range] converts all arguments to [int] before processing.
[b]Note:[/b] Returns an empty array if no value meets the value constraint (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]).
Examples:
[codeblock]
print(range(4)) # Prints [0, 1, 2, 3]
print(range(2, 5)) # Prints [2, 3, 4]
print(range(0, 6, 2)) # Prints [0, 2, 4]
print(range(4, 1, -1)) # Prints [4, 3, 2]
[/codeblock]
To iterate over an [Array] backwards, use:
[codeblock]
var array = [3, 6, 9]
for i in range(array.size() - 1, -1, -1):
print(array[i])
[/codeblock]
Output:
[codeblock lang=text]
9
6
3
[/codeblock]
To iterate over [float], convert them in the loop.
[codeblock]
for i in range (3, 0, -1):
print(i / 10.0)
[/codeblock]
Output:
[codeblock lang=text]
0.3
0.2
0.1
[/codeblock]
</description>
</method>
<method name="type_exists">
<return type="bool" />
<param index="0" name="type" type="StringName" />
<description>
Returns [code]true[/code] if the given [Object]-derived class exists in [ClassDB]. Note that [Variant] data types are not registered in [ClassDB].
[codeblock]
type_exists("Sprite2D") # Returns true
type_exists("NonExistentClass") # Returns false
[/codeblock]
</description>
</method>
</methods>
<constants>
<constant name="PI" value="3.14159265358979">
Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code], or 180 degrees in rotations.
</constant>
<constant name="TAU" value="6.28318530717959">
The circle constant, the circumference of the unit circle in radians. This is equivalent to [code]PI * 2[/code], or 360 degrees in rotations.
</constant>
<constant name="INF" value="inf">
Positive floating-point infinity. This is the result of floating-point division when the divisor is [code]0.0[/code]. For negative infinity, use [code]-INF[/code]. Dividing by [code]-0.0[/code] will result in negative infinity if the numerator is positive, so dividing by [code]0.0[/code] is not the same as dividing by [code]-0.0[/code] (despite [code]0.0 == -0.0[/code] returning [code]true[/code]).
[b]Warning:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead.
</constant>
<constant name="NAN" value="nan">
"Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that [code]!=[/code] always returns [code]true[/code], while other comparison operators always return [code]false[/code]. This is true even when comparing with itself ([code]NAN == NAN[/code] returns [code]false[/code] and [code]NAN != NAN[/code] returns [code]true[/code]). It is returned by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code].
[b]Warning:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead.
</constant>
</constants>
<annotations>
<annotation name="@export">
<return type="void" />
<description>
Mark the following property as exported (editable in the Inspector dock and saved to disk). To control the type of the exported property, use the type hint notation.
[codeblock]
extends Node
enum Direction {LEFT, RIGHT, UP, DOWN}
# Built-in types.
@export var string = ""
@export var int_number = 5
@export var float_number: float = 5
# Enums.
@export var type: Variant.Type
@export var format: Image.Format
@export var direction: Direction
# Resources.
@export var image: Image
@export var custom_resource: CustomResource
# Nodes.
@export var node: Node
@export var custom_node: CustomNode
# Typed arrays.
@export var int_array: Array[int]
@export var direction_array: Array[Direction]
@export var image_array: Array[Image]
@export var node_array: Array[Node]
[/codeblock]
[b]Note:[/b] Custom resources and nodes should be registered as global classes using [code]class_name[/code], since the Inspector currently only supports global classes. Otherwise, a less specific type will be exported instead.
[b]Note:[/b] Node export is only supported in [Node]-derived classes and has a number of other limitations.
</description>
</annotation>
<annotation name="@export_category">
<return type="void" />
<param index="0" name="name" type="String" />
<description>
Define a new category for the following exported properties. This helps to organize properties in the Inspector dock.
See also [constant PROPERTY_USAGE_CATEGORY].
[codeblock]
@export_category("Statistics")
@export var hp = 30
@export var speed = 1.25
[/codeblock]
[b]Note:[/b] Categories in the Inspector dock's list usually divide properties coming from different classes (Node, Node2D, Sprite, etc.). For better clarity, it's recommended to use [annotation @export_group] and [annotation @export_subgroup], instead.
</description>
</annotation>
<annotation name="@export_color_no_alpha">
<return type="void" />
<description>
Export a [Color], [Array][lb][Color][rb], or [PackedColorArray] property without allowing its transparency ([member Color.a]) to be edited.
See also [constant PROPERTY_HINT_COLOR_NO_ALPHA].
[codeblock]
@export_color_no_alpha var dye_color: Color
@export_color_no_alpha var dye_colors: Array[Color]
[/codeblock]
</description>
</annotation>
<annotation name="@export_custom">
<return type="void" />
<param index="0" name="hint" type="int" enum="PropertyHint" />
<param index="1" name="hint_string" type="String" />
<param index="2" name="usage" type="int" enum="PropertyUsageFlags" is_bitfield="true" default="6" />
<description>
Allows you to set a custom hint, hint string, and usage flags for the exported property. Note that there's no validation done in GDScript, it will just pass the parameters to the editor.
[codeblock]
@export_custom(PROPERTY_HINT_NONE, "suffix:m") var suffix: Vector3
[/codeblock]
[b]Note:[/b] Regardless of the [param usage] value, the [constant PROPERTY_USAGE_SCRIPT_VARIABLE] flag is always added, as with any explicitly declared script variable.
</description>
</annotation>
<annotation name="@export_dir">
<return type="void" />
<description>
Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as a path to a directory. The path will be limited to the project folder and its subfolders. See [annotation @export_global_dir] to allow picking from the entire filesystem.
See also [constant PROPERTY_HINT_DIR].
[codeblock]
@export_dir var sprite_folder_path: String
@export_dir var sprite_folder_paths: Array[String]
[/codeblock]
</description>
</annotation>
<annotation name="@export_enum" qualifiers="vararg">
<return type="void" />
<param index="0" name="names" type="String" />
<description>
Export an [int], [String], [Array][lb][int][rb], [Array][lb][String][rb], [PackedByteArray], [PackedInt32Array], [PackedInt64Array], or [PackedStringArray] property as an enumerated list of options (or an array of options). If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add explicit values using a colon. If the property is a [String], then the value is stored.
See also [constant PROPERTY_HINT_ENUM].
[codeblock]
@export_enum("Warrior", "Magician", "Thief") var character_class: int
@export_enum("Slow:30", "Average:60", "Very Fast:200") var character_speed: int
@export_enum("Rebecca", "Mary", "Leah") var character_name: String
@export_enum("Sword", "Spear", "Mace") var character_items: Array[int]
@export_enum("double_jump", "climb", "dash") var character_skills: Array[String]
[/codeblock]
If you want to set an initial value, you must specify it explicitly:
[codeblock]
@export_enum("Rebecca", "Mary", "Leah") var character_name: String = "Rebecca"
[/codeblock]
If you want to use named GDScript enums, then use [annotation @export] instead:
[codeblock]
enum CharacterName {REBECCA, MARY, LEAH}
@export var character_name: CharacterName
enum CharacterItem {SWORD, SPEAR, MACE}
@export var character_items: Array[CharacterItem]
[/codeblock]
</description>
</annotation>
<annotation name="@export_exp_easing" qualifiers="vararg">
<return type="void" />
<param index="0" name="hints" type="String" default="&quot;&quot;" />
<description>
Export a floating-point property with an easing editor widget. Additional hints can be provided to adjust the behavior of the widget. [code]"attenuation"[/code] flips the curve, which makes it more intuitive for editing attenuation properties. [code]"positive_only"[/code] limits values to only be greater than or equal to zero.
See also [constant PROPERTY_HINT_EXP_EASING].
[codeblock]
@export_exp_easing var transition_speed
@export_exp_easing("attenuation") var fading_attenuation
@export_exp_easing("positive_only") var effect_power
@export_exp_easing var speeds: Array[float]
[/codeblock]
</description>
</annotation>
<annotation name="@export_file" qualifiers="vararg">
<return type="void" />
<param index="0" name="filter" type="String" default="&quot;&quot;" />
<description>
Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as a path to a file. The path will be limited to the project folder and its subfolders. See [annotation @export_global_file] to allow picking from the entire filesystem.
If [param filter] is provided, only matching files will be available for picking.
See also [constant PROPERTY_HINT_FILE].
[codeblock]
@export_file var sound_effect_path: String
@export_file("*.txt") var notes_path: String
@export_file var level_paths: Array[String]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags" qualifiers="vararg">
<return type="void" />
<param index="0" name="names" type="String" />
<description>
Export an integer property as a bit flag field. This allows to store several "checked" or [code]true[/code] values with one property, and comfortably select them from the Inspector dock.
See also [constant PROPERTY_HINT_FLAGS].
[codeblock]
@export_flags("Fire", "Water", "Earth", "Wind") var spell_elements = 0
[/codeblock]
You can add explicit values using a colon:
[codeblock]
@export_flags("Self:4", "Allies:8", "Foes:16") var spell_targets = 0
[/codeblock]
You can also combine several flags:
[codeblock]
@export_flags("Self:4", "Allies:8", "Self and Allies:12", "Foes:16")
var spell_targets = 0
[/codeblock]
[b]Note:[/b] A flag value must be at least [code]1[/code] and at most [code]2 ** 32 - 1[/code].
[b]Note:[/b] Unlike [annotation @export_enum], the previous explicit value is not taken into account. In the following example, A is 16, B is 2, C is 4.
[codeblock]
@export_flags("A:16", "B", "C") var x
[/codeblock]
You can also use the annotation on [Array][lb][int][rb], [PackedByteArray], [PackedInt32Array], and [PackedInt64Array]
[codeblock]
@export_flags("Fire", "Water", "Earth", "Wind") var phase_elements: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags_2d_navigation">
<return type="void" />
<description>
Export an integer property as a bit flag field for 2D navigation layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_navigation/layer_1].
See also [constant PROPERTY_HINT_LAYERS_2D_NAVIGATION].
[codeblock]
@export_flags_2d_navigation var navigation_layers: int
@export_flags_2d_navigation var navigation_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags_2d_physics">
<return type="void" />
<description>
Export an integer property as a bit flag field for 2D physics layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_physics/layer_1].
See also [constant PROPERTY_HINT_LAYERS_2D_PHYSICS].
[codeblock]
@export_flags_2d_physics var physics_layers: int
@export_flags_2d_physics var physics_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags_2d_render">
<return type="void" />
<description>
Export an integer property as a bit flag field for 2D render layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_render/layer_1].
See also [constant PROPERTY_HINT_LAYERS_2D_RENDER].
[codeblock]
@export_flags_2d_render var render_layers: int
@export_flags_2d_render var render_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags_3d_navigation">
<return type="void" />
<description>
Export an integer property as a bit flag field for 3D navigation layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_navigation/layer_1].
See also [constant PROPERTY_HINT_LAYERS_3D_NAVIGATION].
[codeblock]
@export_flags_3d_navigation var navigation_layers: int
@export_flags_3d_navigation var navigation_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags_3d_physics">
<return type="void" />
<description>
Export an integer property as a bit flag field for 3D physics layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_physics/layer_1].
See also [constant PROPERTY_HINT_LAYERS_3D_PHYSICS].
[codeblock]
@export_flags_3d_physics var physics_layers: int
@export_flags_3d_physics var physics_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags_3d_render">
<return type="void" />
<description>
Export an integer property as a bit flag field for 3D render layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_render/layer_1].
See also [constant PROPERTY_HINT_LAYERS_3D_RENDER].
[codeblock]
@export_flags_3d_render var render_layers: int
@export_flags_3d_render var render_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_flags_avoidance">
<return type="void" />
<description>
Export an integer property as a bit flag field for navigation avoidance layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/avoidance/layer_1].
See also [constant PROPERTY_HINT_LAYERS_AVOIDANCE].
[codeblock]
@export_flags_avoidance var avoidance_layers: int
@export_flags_avoidance var avoidance_layers_array: Array[int]
[/codeblock]
</description>
</annotation>
<annotation name="@export_global_dir">
<return type="void" />
<description>
Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as an absolute path to a directory. The path can be picked from the entire filesystem. See [annotation @export_dir] to limit it to the project folder and its subfolders.
See also [constant PROPERTY_HINT_GLOBAL_DIR].
[codeblock]
@export_global_dir var sprite_folder_path: String
@export_global_dir var sprite_folder_paths: Array[String]
[/codeblock]
</description>
</annotation>
<annotation name="@export_global_file" qualifiers="vararg">
<return type="void" />
<param index="0" name="filter" type="String" default="&quot;&quot;" />
<description>
Export a [String], [Array][lb][String][rb], or [PackedStringArray] property as an absolute path to a file. The path can be picked from the entire filesystem. See [annotation @export_file] to limit it to the project folder and its subfolders.
If [param filter] is provided, only matching files will be available for picking.
See also [constant PROPERTY_HINT_GLOBAL_FILE].
[codeblock]
@export_global_file var sound_effect_path: String
@export_global_file("*.txt") var notes_path: String
@export_global_file var multiple_paths: Array[String]
[/codeblock]
</description>
</annotation>
<annotation name="@export_group">
<return type="void" />
<param index="0" name="name" type="String" />
<param index="1" name="prefix" type="String" default="&quot;&quot;" />
<description>
Define a new group for the following exported properties. This helps to organize properties in the Inspector dock. Groups can be added with an optional [param prefix], which would make group to only consider properties that have this prefix. The grouping will break on the first property that doesn't have a prefix. The prefix is also removed from the property's name in the Inspector dock.
If no [param prefix] is provided, then every following property will be added to the group. The group ends when then next group or category is defined. You can also force end a group by using this annotation with empty strings for parameters, [code]@export_group("", "")[/code].
Groups cannot be nested, use [annotation @export_subgroup] to add subgroups within groups.
See also [constant PROPERTY_USAGE_GROUP].
[codeblock]
@export_group("Racer Properties")
@export var nickname = "Nick"
@export var age = 26
@export_group("Car Properties", "car_")
@export var car_label = "Speedy"
@export var car_number = 3
@export_group("", "")
@export var ungrouped_number = 3
[/codeblock]
</description>
</annotation>
<annotation name="@export_multiline">
<return type="void" />
<description>
Export a [String], [Array][lb][String][rb], [PackedStringArray], [Dictionary] or [Array][lb][Dictionary][rb] property with a large [TextEdit] widget instead of a [LineEdit]. This adds support for multiline content and makes it easier to edit large amount of text stored in the property.
See also [constant PROPERTY_HINT_MULTILINE_TEXT].
[codeblock]
@export_multiline var character_biography
@export_multiline var npc_dialogs: Array[String]
[/codeblock]
</description>
</annotation>
<annotation name="@export_node_path" qualifiers="vararg">
<return type="void" />
<param index="0" name="type" type="String" default="&quot;&quot;" />
<description>
Export a [NodePath] or [Array][lb][NodePath][rb] property with a filter for allowed node types.
See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES].
[codeblock]
@export_node_path("Button", "TouchScreenButton") var some_button
@export_node_path("Button", "TouchScreenButton") var many_buttons: Array[NodePath]
[/codeblock]
[b]Note:[/b] The type must be a native class or a globally registered script (using the [code]class_name[/code] keyword) that inherits [Node].
</description>
</annotation>
<annotation name="@export_placeholder">
<return type="void" />
<param index="0" name="placeholder" type="String" />
<description>
Export a [String], [Array][lb][String][rb], or [PackedStringArray] property with a placeholder text displayed in the editor widget when no value is present.
See also [constant PROPERTY_HINT_PLACEHOLDER_TEXT].
[codeblock]
@export_placeholder("Name in lowercase") var character_id: String
@export_placeholder("Name in lowercase") var friend_ids: Array[String]
[/codeblock]
</description>
</annotation>
<annotation name="@export_range" qualifiers="vararg">
<return type="void" />
<param index="0" name="min" type="float" />
<param index="1" name="max" type="float" />
<param index="2" name="step" type="float" default="1.0" />
<param index="3" name="extra_hints" type="String" default="&quot;&quot;" />
<description>
Export an [int], [float], [Array][lb][int][rb], [Array][lb][float][rb], [PackedByteArray], [PackedInt32Array], [PackedInt64Array], [PackedFloat32Array], or [PackedFloat64Array] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [member EditorSettings.interface/inspector/default_float_step] setting.
If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget.
Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string.
See also [constant PROPERTY_HINT_RANGE].
[codeblock]
@export_range(0, 20) var number
@export_range(-10, 20) var number
@export_range(-10, 20, 0.2) var number: float
@export_range(0, 20) var numbers: Array[float]
@export_range(0, 100, 1, "or_greater") var power_percent
@export_range(0, 100, 1, "or_greater", "or_less") var health_delta
@export_range(-180, 180, 0.001, "radians_as_degrees") var angle_radians
@export_range(0, 360, 1, "degrees") var angle_degrees
@export_range(-8, 8, 2, "suffix:px") var target_offset
[/codeblock]
</description>
</annotation>
<annotation name="@export_storage">
<return type="void" />
<description>
Export a property with [constant PROPERTY_USAGE_STORAGE] flag. The property is not displayed in the editor, but it is serialized and stored in the scene or resource file. This can be useful for [annotation @tool] scripts. Also the property value is copied when [method Resource.duplicate] or [method Node.duplicate] is called, unlike non-exported variables.
[codeblock]
var a # Not stored in the file, not displayed in the editor.
@export_storage var b # Stored in the file, not displayed in the editor.
@export var c: int # Stored in the file, displayed in the editor.
[/codeblock]
</description>
</annotation>
<annotation name="@export_subgroup">
<return type="void" />
<param index="0" name="name" type="String" />
<param index="1" name="prefix" type="String" default="&quot;&quot;" />
<description>
Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock. Subgroups work exactly like groups, except they need a parent group to exist. See [annotation @export_group].
See also [constant PROPERTY_USAGE_SUBGROUP].
[codeblock]
@export_group("Racer Properties")
@export var nickname = "Nick"
@export var age = 26
@export_subgroup("Car Properties", "car_")
@export var car_label = "Speedy"
@export var car_number = 3
[/codeblock]
[b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups.
</description>
</annotation>
<annotation name="@icon">
<return type="void" />
<param index="0" name="icon_path" type="String" />
<description>
Add a custom icon to the current script. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as in various editor dialogs.
[codeblock]
@icon("res://path/to/class/icon.svg")
[/codeblock]
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
[b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
[b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
</description>
</annotation>
<annotation name="@onready">
<return type="void" />
<description>
Mark the following property as assigned when the [Node] is ready. Values for these properties are not assigned immediately when the node is initialized ([method Object._init]), and instead are computed and stored right before [method Node._ready].
[codeblock]
@onready var character_name: Label = $Label
[/codeblock]
</description>
</annotation>
<annotation name="@rpc">
<return type="void" />
<param index="0" name="mode" type="String" default="&quot;authority&quot;" />
<param index="1" name="sync" type="String" default="&quot;call_remote&quot;" />
<param index="2" name="transfer_mode" type="String" default="&quot;unreliable&quot;" />
<param index="3" name="transfer_channel" type="int" default="0" />
<description>
Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url].
If [param mode] is set as [code]"any_peer"[/code], allows any peer to call this RPC function. Otherwise, only the authority peer is allowed to call it and [param mode] should be kept as [code]"authority"[/code]. When configuring functions as RPCs with [method Node.rpc_config], each of these modes respectively corresponds to the [constant MultiplayerAPI.RPC_MODE_AUTHORITY] and [constant MultiplayerAPI.RPC_MODE_ANY_PEER] RPC modes. See [enum MultiplayerAPI.RPCMode]. If a peer that is not the authority tries to call a function that is only allowed for the authority, the function will not be executed. If the error can be detected locally (when the RPC configuration is consistent between the local and the remote peer), an error message will be displayed on the sender peer. Otherwise, the remote peer will detect the error and print an error there.
If [param sync] is set as [code]"call_remote"[/code], the function will only be executed on the remote peer, but not locally. To run this function locally too, set [param sync] to [code]"call_local"[/code]. When configuring functions as RPCs with [method Node.rpc_config], this is equivalent to setting [code]call_local[/code] to [code]true[/code].
The [param transfer_mode] accepted values are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code], or [code]"reliable"[/code]. It sets the transfer mode of the underlying [MultiplayerPeer]. See [member MultiplayerPeer.transfer_mode].
The [param transfer_channel] defines the channel of the underlying [MultiplayerPeer]. See [member MultiplayerPeer.transfer_channel].
The order of [param mode], [param sync] and [param transfer_mode] does not matter, but values related to the same argument must not be used more than once. [param transfer_channel] always has to be the 4th argument (you must specify 3 preceding arguments).
[codeblock]
@rpc
func fn(): pass
@rpc("any_peer", "unreliable_ordered")
func fn_update_pos(): pass
@rpc("authority", "call_remote", "unreliable", 0) # Equivalent to @rpc
func fn_default(): pass
[/codeblock]
</description>
</annotation>
<annotation name="@static_unload">
<return type="void" />
<description>
Make a script with static variables to not persist after all references are lost. If the script is loaded again the static variables will revert to their default values.
[b]Note:[/b] As annotations describe their subject, the [annotation @static_unload] annotation must be placed before the class definition and inheritance.
[b]Warning:[/b] Currently, due to a bug, scripts are never freed, even if [annotation @static_unload] annotation is used.
</description>
</annotation>
<annotation name="@tool">
<return type="void" />
<description>
Mark the current script as a tool script, allowing it to be loaded and executed by the editor. See [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]Running code in the editor[/url].
[codeblock]
@tool
extends Node
[/codeblock]
[b]Note:[/b] As annotations describe their subject, the [annotation @tool] annotation must be placed before the class definition and inheritance.
</description>
</annotation>
<annotation name="@warning_ignore" qualifiers="vararg">
<return type="void" />
<param index="0" name="warning" type="String" />
<description>
Mark the following statement to ignore the specified [param warning]. See [url=$DOCS_URL/tutorials/scripting/gdscript/warning_system.html]GDScript warning system[/url].
[codeblock]
func test():
print("hello")
return
@warning_ignore("unreachable_code")
print("unreachable")
[/codeblock]
</description>
</annotation>
</annotations>
</class>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GDScript" inherits="Script" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A script implemented in the GDScript programming language.
</brief_description>
<description>
A script implemented in the GDScript programming language, saved with the [code].gd[/code] extension. The script extends the functionality of all objects that instantiate it.
Calling [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.
If you are looking for GDScript's built-in functions, see [@GDScript] instead.
</description>
<tutorials>
<link title="GDScript documentation index">$DOCS_URL/tutorials/scripting/gdscript/index.html</link>
</tutorials>
<methods>
<method name="new" qualifiers="vararg">
<return type="Variant" />
<description>
Returns a new instance of the script.
For example:
[codeblock]
var MyClass = load("myclass.gd")
var instance = MyClass.new()
assert(instance.get_script() == MyClass)
[/codeblock]
</description>
</method>
</methods>
</class>

View file

@ -0,0 +1,510 @@
/**************************************************************************/
/* gdscript_docgen.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_docgen.h"
#include "../gdscript.h"
#include "core/config/project_settings.h"
HashMap<String, String> GDScriptDocGen::singletons;
String GDScriptDocGen::_get_script_name(const String &p_path) {
const HashMap<String, String>::ConstIterator E = singletons.find(p_path);
if (E) {
return E->value;
}
return p_path.trim_prefix("res://").quote();
}
String GDScriptDocGen::_get_class_name(const GDP::ClassNode &p_class) {
const GDP::ClassNode *curr_class = &p_class;
if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
return _get_script_name(curr_class->fqcn);
}
String full_name = curr_class->identifier->name;
while (curr_class->outer) {
curr_class = curr_class->outer;
if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class.
return vformat("%s.%s", _get_script_name(curr_class->fqcn), full_name);
}
full_name = vformat("%s.%s", curr_class->identifier->name, full_name);
}
return full_name;
}
void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
if (!p_gdtype.is_hard_type()) {
r_type = "Variant";
return;
}
switch (p_gdtype.kind) {
case GDType::BUILTIN:
if (p_gdtype.builtin_type == Variant::NIL) {
r_type = p_is_return ? "void" : "null";
return;
}
if (p_gdtype.builtin_type == Variant::ARRAY && p_gdtype.has_container_element_type(0)) {
_doctype_from_gdtype(p_gdtype.get_container_element_type(0), r_type, r_enum);
if (!r_enum.is_empty()) {
r_type = "int[]";
r_enum += "[]";
return;
}
if (!r_type.is_empty() && r_type != "Variant") {
r_type += "[]";
return;
}
}
r_type = Variant::get_type_name(p_gdtype.builtin_type);
return;
case GDType::NATIVE:
if (p_gdtype.is_meta_type) {
//r_type = GDScriptNativeClass::get_class_static();
r_type = "Object"; // "GDScriptNativeClass" refers to a blank page.
return;
}
r_type = p_gdtype.native_type;
return;
case GDType::SCRIPT:
if (p_gdtype.is_meta_type) {
r_type = p_gdtype.script_type.is_valid() ? p_gdtype.script_type->get_class() : Script::get_class_static();
return;
}
if (p_gdtype.script_type.is_valid()) {
if (p_gdtype.script_type->get_global_name() != StringName()) {
r_type = p_gdtype.script_type->get_global_name();
return;
}
if (!p_gdtype.script_type->get_path().is_empty()) {
r_type = _get_script_name(p_gdtype.script_type->get_path());
return;
}
}
if (!p_gdtype.script_path.is_empty()) {
r_type = _get_script_name(p_gdtype.script_path);
return;
}
r_type = "Object";
return;
case GDType::CLASS:
if (p_gdtype.is_meta_type) {
r_type = GDScript::get_class_static();
return;
}
r_type = _get_class_name(*p_gdtype.class_type);
return;
case GDType::ENUM:
if (p_gdtype.is_meta_type) {
r_type = "Dictionary";
return;
}
r_type = "int";
r_enum = String(p_gdtype.native_type).replace("::", ".");
if (r_enum.begins_with("res://")) {
r_enum = r_enum.trim_prefix("res://");
int dot_pos = r_enum.rfind(".");
if (dot_pos >= 0) {
r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
}
}
return;
case GDType::VARIANT:
case GDType::RESOLVING:
case GDType::UNRESOLVED:
r_type = "Variant";
return;
}
}
String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_recursion_level) {
constexpr int MAX_RECURSION_LEVEL = 2;
switch (p_variant.get_type()) {
case Variant::STRING:
return String(p_variant).c_escape().quote();
case Variant::OBJECT:
return "<Object>";
case Variant::DICTIONARY: {
const Dictionary dict = p_variant;
if (dict.is_empty()) {
return "{}";
}
if (p_recursion_level > MAX_RECURSION_LEVEL) {
return "{...}";
}
List<Variant> keys;
dict.get_key_list(&keys);
keys.sort();
String data;
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
data += ", ";
}
data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
}
return "{" + data + "}";
} break;
case Variant::ARRAY: {
const Array array = p_variant;
String result;
if (array.get_typed_builtin() != Variant::NIL) {
result += "Array[";
Ref<Script> script = array.get_typed_script();
if (script.is_valid()) {
if (script->get_global_name() != StringName()) {
result += script->get_global_name();
} else if (!script->get_path().get_file().is_empty()) {
result += script->get_path().get_file();
} else {
result += array.get_typed_class_name();
}
} else if (array.get_typed_class_name() != StringName()) {
result += array.get_typed_class_name();
} else {
result += Variant::get_type_name((Variant::Type)array.get_typed_builtin());
}
result += "](";
}
if (array.is_empty()) {
result += "[]";
} else if (p_recursion_level > MAX_RECURSION_LEVEL) {
result += "[...]";
} else {
result += "[";
for (int i = 0; i < array.size(); i++) {
if (i > 0) {
result += ", ";
}
result += _docvalue_from_variant(array[i], p_recursion_level + 1);
}
result += "]";
}
if (array.get_typed_builtin() != Variant::NIL) {
result += ")";
}
return result;
} break;
default:
return p_variant.get_construct_string();
}
}
String GDScriptDocGen::_docvalue_from_expression(const GDP::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, String());
if (p_expression->is_constant) {
return _docvalue_from_variant(p_expression->reduced_value);
}
switch (p_expression->type) {
case GDP::Node::ARRAY: {
const GDP::ArrayNode *array = static_cast<const GDP::ArrayNode *>(p_expression);
return array->elements.is_empty() ? "[]" : "[...]";
} break;
case GDP::Node::CALL: {
const GDP::CallNode *call = static_cast<const GDP::CallNode *>(p_expression);
return call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)");
} break;
case GDP::Node::DICTIONARY: {
const GDP::DictionaryNode *dict = static_cast<const GDP::DictionaryNode *>(p_expression);
return dict->elements.is_empty() ? "{}" : "{...}";
} break;
case GDP::Node::IDENTIFIER: {
const GDP::IdentifierNode *id = static_cast<const GDP::IdentifierNode *>(p_expression);
return id->name;
} break;
default: {
return "<unknown>";
} break;
}
}
void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
p_script->_clear_doc();
DocData::ClassDoc &doc = p_script->doc;
doc.is_script_doc = true;
if (p_script->local_name == StringName()) {
// This is an outer unnamed class.
doc.name = _get_script_name(p_script->get_script_path());
} else {
// This is an inner or global outer class.
doc.name = p_script->local_name;
if (p_script->_owner) {
doc.name = p_script->_owner->doc.name + "." + doc.name;
}
}
doc.script_path = p_script->get_script_path();
if (p_script->base.is_valid() && p_script->base->is_valid()) {
if (!p_script->base->doc.name.is_empty()) {
doc.inherits = p_script->base->doc.name;
} else {
doc.inherits = p_script->base->get_instance_base_type();
}
} else if (p_script->native.is_valid()) {
doc.inherits = p_script->native->get_name();
}
doc.brief_description = p_class->doc_data.brief;
doc.description = p_class->doc_data.description;
for (const Pair<String, String> &p : p_class->doc_data.tutorials) {
DocData::TutorialDoc td;
td.title = p.first;
td.link = p.second;
doc.tutorials.append(td);
}
doc.is_deprecated = p_class->doc_data.is_deprecated;
doc.deprecated_message = p_class->doc_data.deprecated_message;
doc.is_experimental = p_class->doc_data.is_experimental;
doc.experimental_message = p_class->doc_data.experimental_message;
for (const GDP::ClassNode::Member &member : p_class->members) {
switch (member.type) {
case GDP::ClassNode::Member::CLASS: {
const GDP::ClassNode *inner_class = member.m_class;
const StringName &class_name = inner_class->identifier->name;
p_script->member_lines[class_name] = inner_class->start_line;
// Recursively generate inner class docs.
// Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts().
GDScriptDocGen::_generate_docs(*p_script->subclasses[class_name], inner_class);
} break;
case GDP::ClassNode::Member::CONSTANT: {
const GDP::ConstantNode *m_const = member.constant;
const StringName &const_name = member.constant->identifier->name;
p_script->member_lines[const_name] = m_const->start_line;
DocData::ConstantDoc const_doc;
const_doc.name = const_name;
const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value);
const_doc.is_value_valid = true;
const_doc.description = m_const->doc_data.description;
const_doc.is_deprecated = m_const->doc_data.is_deprecated;
const_doc.deprecated_message = m_const->doc_data.deprecated_message;
const_doc.is_experimental = m_const->doc_data.is_experimental;
const_doc.experimental_message = m_const->doc_data.experimental_message;
doc.constants.push_back(const_doc);
} break;
case GDP::ClassNode::Member::FUNCTION: {
const GDP::FunctionNode *m_func = member.function;
const StringName &func_name = m_func->identifier->name;
p_script->member_lines[func_name] = m_func->start_line;
DocData::MethodDoc method_doc;
method_doc.name = func_name;
method_doc.description = m_func->doc_data.description;
method_doc.is_deprecated = m_func->doc_data.is_deprecated;
method_doc.deprecated_message = m_func->doc_data.deprecated_message;
method_doc.is_experimental = m_func->doc_data.is_experimental;
method_doc.experimental_message = m_func->doc_data.experimental_message;
method_doc.qualifiers = m_func->is_static ? "static" : "";
if (m_func->return_type) {
// `m_func->return_type->get_datatype()` is a metatype.
_doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
} else if (!m_func->body->has_return) {
// If no `return` statement, then return type is `void`, not `Variant`.
method_doc.return_type = "void";
} else {
method_doc.return_type = "Variant";
}
for (const GDP::ParameterNode *p : m_func->parameters) {
DocData::ArgumentDoc arg_doc;
arg_doc.name = p->identifier->name;
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
if (p->initializer != nullptr) {
arg_doc.default_value = _docvalue_from_expression(p->initializer);
}
method_doc.arguments.push_back(arg_doc);
}
doc.methods.push_back(method_doc);
} break;
case GDP::ClassNode::Member::SIGNAL: {
const GDP::SignalNode *m_signal = member.signal;
const StringName &signal_name = m_signal->identifier->name;
p_script->member_lines[signal_name] = m_signal->start_line;
DocData::MethodDoc signal_doc;
signal_doc.name = signal_name;
signal_doc.description = m_signal->doc_data.description;
signal_doc.is_deprecated = m_signal->doc_data.is_deprecated;
signal_doc.deprecated_message = m_signal->doc_data.deprecated_message;
signal_doc.is_experimental = m_signal->doc_data.is_experimental;
signal_doc.experimental_message = m_signal->doc_data.experimental_message;
for (const GDP::ParameterNode *p : m_signal->parameters) {
DocData::ArgumentDoc arg_doc;
arg_doc.name = p->identifier->name;
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
signal_doc.arguments.push_back(arg_doc);
}
doc.signals.push_back(signal_doc);
} break;
case GDP::ClassNode::Member::VARIABLE: {
const GDP::VariableNode *m_var = member.variable;
const StringName &var_name = m_var->identifier->name;
p_script->member_lines[var_name] = m_var->start_line;
DocData::PropertyDoc prop_doc;
prop_doc.name = var_name;
prop_doc.description = m_var->doc_data.description;
prop_doc.is_deprecated = m_var->doc_data.is_deprecated;
prop_doc.deprecated_message = m_var->doc_data.deprecated_message;
prop_doc.is_experimental = m_var->doc_data.is_experimental;
prop_doc.experimental_message = m_var->doc_data.experimental_message;
_doctype_from_gdtype(m_var->get_datatype(), prop_doc.type, prop_doc.enumeration);
switch (m_var->property) {
case GDP::VariableNode::PROP_NONE:
break;
case GDP::VariableNode::PROP_INLINE:
if (m_var->setter != nullptr) {
prop_doc.setter = m_var->setter->identifier->name;
}
if (m_var->getter != nullptr) {
prop_doc.getter = m_var->getter->identifier->name;
}
break;
case GDP::VariableNode::PROP_SETGET:
if (m_var->setter_pointer != nullptr) {
prop_doc.setter = m_var->setter_pointer->name;
}
if (m_var->getter_pointer != nullptr) {
prop_doc.getter = m_var->getter_pointer->name;
}
break;
}
if (m_var->initializer != nullptr) {
prop_doc.default_value = _docvalue_from_expression(m_var->initializer);
}
prop_doc.overridden = false;
doc.properties.push_back(prop_doc);
} break;
case GDP::ClassNode::Member::ENUM: {
const GDP::EnumNode *m_enum = member.m_enum;
StringName name = m_enum->identifier->name;
p_script->member_lines[name] = m_enum->start_line;
DocData::EnumDoc enum_doc;
enum_doc.description = m_enum->doc_data.description;
enum_doc.is_deprecated = m_enum->doc_data.is_deprecated;
enum_doc.deprecated_message = m_enum->doc_data.deprecated_message;
enum_doc.is_experimental = m_enum->doc_data.is_experimental;
enum_doc.experimental_message = m_enum->doc_data.experimental_message;
doc.enums[name] = enum_doc;
for (const GDP::EnumNode::Value &val : m_enum->values) {
DocData::ConstantDoc const_doc;
const_doc.name = val.identifier->name;
const_doc.value = _docvalue_from_variant(val.value);
const_doc.is_value_valid = true;
const_doc.enumeration = name;
const_doc.description = val.doc_data.description;
const_doc.is_deprecated = val.doc_data.is_deprecated;
const_doc.deprecated_message = val.doc_data.deprecated_message;
const_doc.is_experimental = val.doc_data.is_experimental;
const_doc.experimental_message = val.doc_data.experimental_message;
doc.constants.push_back(const_doc);
}
} break;
case GDP::ClassNode::Member::ENUM_VALUE: {
const GDP::EnumNode::Value &m_enum_val = member.enum_value;
const StringName &name = m_enum_val.identifier->name;
p_script->member_lines[name] = m_enum_val.identifier->start_line;
DocData::ConstantDoc const_doc;
const_doc.name = name;
const_doc.value = _docvalue_from_variant(m_enum_val.value);
const_doc.is_value_valid = true;
const_doc.enumeration = "@unnamed_enums";
const_doc.description = m_enum_val.doc_data.description;
const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
const_doc.deprecated_message = m_enum_val.doc_data.deprecated_message;
const_doc.is_experimental = m_enum_val.doc_data.is_experimental;
const_doc.experimental_message = m_enum_val.doc_data.experimental_message;
doc.constants.push_back(const_doc);
} break;
default:
break;
}
}
// Add doc to the outer-most class.
p_script->_add_doc(doc);
}
void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) {
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
if (E.value.is_singleton) {
singletons[E.value.path] = E.key;
}
}
_generate_docs(p_script, p_class);
singletons.clear();
}

View file

@ -0,0 +1,55 @@
/**************************************************************************/
/* gdscript_docgen.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_DOCGEN_H
#define GDSCRIPT_DOCGEN_H
#include "../gdscript_parser.h"
#include "core/doc_data.h"
class GDScriptDocGen {
using GDP = GDScriptParser;
using GDType = GDP::DataType;
static HashMap<String, String> singletons; // Script path to singleton name.
static String _get_script_name(const String &p_path);
static String _get_class_name(const GDP::ClassNode &p_class);
static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1);
static String _docvalue_from_expression(const GDP::ExpressionNode *p_expression);
static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
public:
static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
};
#endif // GDSCRIPT_DOCGEN_H

View file

@ -0,0 +1,969 @@
/**************************************************************************/
/* gdscript_highlighter.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_highlighter.h"
#include "../gdscript.h"
#include "../gdscript_tokenizer.h"
#include "core/config/project_settings.h"
#include "editor/editor_settings.h"
#include "editor/themes/editor_theme_manager.h"
Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
Dictionary color_map;
Type next_type = NONE;
Type current_type = NONE;
Type prev_type = NONE;
String prev_text = "";
int prev_column = 0;
bool prev_is_char = false;
bool prev_is_digit = false;
bool prev_is_binary_op = false;
bool in_keyword = false;
bool in_word = false;
bool in_number = false;
bool in_node_path = false;
bool in_node_ref = false;
bool in_annotation = false;
bool in_string_name = false;
bool is_hex_notation = false;
bool is_bin_notation = false;
bool in_member_variable = false;
bool in_lambda = false;
bool in_function_name = false; // Any call.
bool in_function_declaration = false; // Only declaration.
bool in_signal_declaration = false;
bool is_after_func_signal_declaration = false;
bool in_var_const_declaration = false;
bool is_after_var_const_declaration = false;
bool expect_type = false;
int in_declaration_params = 0; // The number of opened `(` after func/signal name.
int in_declaration_param_dicts = 0; // The number of opened `{` inside func params.
int in_type_params = 0; // The number of opened `[` after type name.
Color keyword_color;
Color color;
color_region_cache[p_line] = -1;
int in_region = -1;
if (p_line != 0) {
int prev_region_line = p_line - 1;
while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) {
prev_region_line--;
}
for (int i = prev_region_line; i < p_line - 1; i++) {
get_line_syntax_highlighting(i);
}
if (!color_region_cache.has(p_line - 1)) {
get_line_syntax_highlighting(p_line - 1);
}
in_region = color_region_cache[p_line - 1];
}
const String &str = text_edit->get_line(p_line);
const int line_length = str.length();
Color prev_color;
if (in_region != -1 && line_length == 0) {
color_region_cache[p_line] = in_region;
}
for (int j = 0; j < line_length; j++) {
Dictionary highlighter_info;
color = font_color;
bool is_char = !is_symbol(str[j]);
bool is_a_symbol = is_symbol(str[j]);
bool is_a_digit = is_digit(str[j]);
bool is_binary_op = false;
/* color regions */
if (is_a_symbol || in_region != -1) {
int from = j;
if (in_region == -1) {
for (; from < line_length; from++) {
if (str[from] == '\\') {
from++;
continue;
}
break;
}
}
if (from != line_length) {
// Check if we are in entering a region.
if (in_region == -1) {
const bool r_prefix = from > 0 && str[from - 1] == 'r';
for (int c = 0; c < color_regions.size(); c++) {
// Check there is enough room.
int chars_left = line_length - from;
int start_key_length = color_regions[c].start_key.length();
int end_key_length = color_regions[c].end_key.length();
if (chars_left < start_key_length) {
continue;
}
if (color_regions[c].is_string && color_regions[c].r_prefix != r_prefix) {
continue;
}
// Search the line.
bool match = true;
const char32_t *start_key = color_regions[c].start_key.get_data();
for (int k = 0; k < start_key_length; k++) {
if (start_key[k] != str[from + k]) {
match = false;
break;
}
}
if (!match) {
continue;
}
in_region = c;
from += start_key_length;
// Check if it's the whole line.
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
// Don't skip comments, for highlighting markers.
if (color_regions[in_region].is_comment) {
break;
}
if (from + end_key_length > line_length) {
// If it's key length and there is a '\', dont skip to highlight esc chars.
if (str.find("\\", from) >= 0) {
break;
}
}
prev_color = color_regions[in_region].color;
highlighter_info["color"] = color_regions[c].color;
color_map[j] = highlighter_info;
j = line_length;
if (!color_regions[c].line_only) {
color_region_cache[p_line] = c;
}
}
break;
}
// Don't skip comments, for highlighting markers.
if (j == line_length && !color_regions[in_region].is_comment) {
continue;
}
}
// If we are in one, find the end key.
if (in_region != -1) {
Color region_color = color_regions[in_region].color;
if (in_node_path && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
region_color = node_path_color;
}
if (in_node_ref && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
region_color = node_ref_color;
}
if (in_string_name && color_regions[in_region].type == ColorRegion::TYPE_STRING) {
region_color = string_name_color;
}
prev_color = region_color;
highlighter_info["color"] = region_color;
color_map[j] = highlighter_info;
if (color_regions[in_region].is_comment) {
int marker_start_pos = from;
int marker_len = 0;
while (from <= line_length) {
if (from < line_length && is_unicode_identifier_continue(str[from])) {
marker_len++;
} else {
if (marker_len > 0) {
HashMap<String, CommentMarkerLevel>::ConstIterator E = comment_markers.find(str.substr(marker_start_pos, marker_len));
if (E) {
Dictionary marker_highlighter_info;
marker_highlighter_info["color"] = comment_marker_colors[E->value];
color_map[marker_start_pos] = marker_highlighter_info;
Dictionary marker_continue_highlighter_info;
marker_continue_highlighter_info["color"] = region_color;
color_map[from] = marker_continue_highlighter_info;
}
}
marker_start_pos = from + 1;
marker_len = 0;
}
from++;
}
from = line_length - 1;
j = from;
} else {
// Search the line.
int region_end_index = -1;
int end_key_length = color_regions[in_region].end_key.length();
const char32_t *end_key = color_regions[in_region].end_key.get_data();
for (; from < line_length; from++) {
if (line_length - from < end_key_length) {
// Don't break if '\' to highlight esc chars.
if (str.find("\\", from) < 0) {
break;
}
}
if (!is_symbol(str[from])) {
continue;
}
if (str[from] == '\\') {
if (!color_regions[in_region].r_prefix) {
Dictionary escape_char_highlighter_info;
escape_char_highlighter_info["color"] = symbol_color;
color_map[from] = escape_char_highlighter_info;
}
from++;
if (!color_regions[in_region].r_prefix) {
int esc_len = 0;
if (str[from] == 'u') {
esc_len = 4;
} else if (str[from] == 'U') {
esc_len = 6;
}
for (int k = 0; k < esc_len && from < line_length - 1; k++) {
if (!is_hex_digit(str[from + 1])) {
break;
}
from++;
}
Dictionary region_continue_highlighter_info;
region_continue_highlighter_info["color"] = region_color;
color_map[from + 1] = region_continue_highlighter_info;
}
continue;
}
region_end_index = from;
for (int k = 0; k < end_key_length; k++) {
if (end_key[k] != str[from + k]) {
region_end_index = -1;
break;
}
}
if (region_end_index != -1) {
break;
}
}
j = from + (end_key_length - 1);
if (region_end_index == -1) {
color_region_cache[p_line] = in_region;
}
}
prev_type = REGION;
prev_text = "";
prev_column = j;
in_region = -1;
prev_is_char = false;
prev_is_digit = false;
prev_is_binary_op = false;
continue;
}
}
}
// VERY hacky... but couldn't come up with anything better.
if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~' || str[j] == '.')) {
int to = j - 1;
// Find what the last text was (prev_text won't work if there's no whitespace, so we need to do it manually).
while (to > 0 && is_whitespace(str[to])) {
to--;
}
int from = to;
while (from > 0 && !is_symbol(str[from])) {
from--;
}
String word = str.substr(from + 1, to - from);
// Keywords need to be exceptions, except for keywords that represent a value.
if (word == "true" || word == "false" || word == "null" || word == "PI" || word == "TAU" || word == "INF" || word == "NAN" || word == "self" || word == "super" || !reserved_keywords.has(word)) {
if (!is_symbol(str[to]) || str[to] == '"' || str[to] == '\'' || str[to] == ')' || str[to] == ']' || str[to] == '}') {
is_binary_op = true;
}
}
}
if (!is_char) {
in_keyword = false;
}
// Allow ABCDEF in hex notation.
if (is_hex_notation && (is_hex_digit(str[j]) || is_a_digit)) {
is_a_digit = true;
} else if (str[j] != '_') {
is_hex_notation = false;
}
// Disallow anything not a 0 or 1 in binary notation.
if (is_bin_notation && !is_binary_digit(str[j])) {
is_a_digit = false;
is_bin_notation = false;
}
if (!in_number && !in_word && is_a_digit) {
in_number = true;
}
// Special cases for numbers.
if (in_number && !is_a_digit) {
if (str[j] == 'b' && str[j - 1] == '0') {
is_bin_notation = true;
} else if (str[j] == 'x' && str[j - 1] == '0') {
is_hex_notation = true;
} else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) &&
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) &&
!(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) &&
!(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) {
/* This condition continues number highlighting in special cases.
1st row: '+' or '-' after scientific notation (like 3e-4);
2nd row: '_' as a numeric separator;
3rd row: Scientific notation 'e' and floating points;
4th row: Floating points inside the number, or leading if after a unary mathematical operator;
5th row: Multiple unary mathematical operators (like ~-7) */
in_number = false;
}
} else if (str[j] == '.' && !is_binary_op && is_digit(str[j + 1]) && (j == 0 || (j > 0 && str[j - 1] != '.'))) {
// Start number highlighting from leading decimal points (like .42)
in_number = true;
} else if ((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op) {
// Only start number highlighting on unary operators if a digit follows them.
int non_op = j + 1;
while (str[non_op] == '-' || str[non_op] == '+' || str[non_op] == '~') {
non_op++;
}
if (is_digit(str[non_op]) || (str[non_op] == '.' && non_op < line_length && is_digit(str[non_op + 1]))) {
in_number = true;
}
}
if (!in_word && is_unicode_identifier_start(str[j]) && !in_number) {
in_word = true;
}
if (is_a_symbol && str[j] != '.' && in_word) {
in_word = false;
}
if (!in_keyword && is_char && !prev_is_char) {
int to = j;
while (to < line_length && !is_symbol(str[to])) {
to++;
}
String word = str.substr(j, to - j);
Color col;
if (global_functions.has(word)) {
// "assert" and "preload" are reserved, so highlight even if not followed by a bracket.
if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) {
col = global_function_color;
} else {
// For other global functions, check if followed by bracket.
int k = to;
while (k < line_length && is_whitespace(str[k])) {
k++;
}
if (str[k] == '(') {
col = global_function_color;
}
}
} else if (class_names.has(word)) {
col = class_names[word];
} else if (reserved_keywords.has(word)) {
col = reserved_keywords[word];
// Don't highlight `list` as a type in `for elem: Type in list`.
expect_type = false;
} 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(); // Keyword, member & global func indexing not allowed.
break;
} else if (str[k] > 32) {
break;
}
}
if (col != Color()) {
in_keyword = true;
keyword_color = col;
}
}
}
if (!in_function_name && in_word && !in_keyword) {
if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::SIGNAL)) {
in_signal_declaration = true;
} else {
int k = j;
while (k < line_length && !is_symbol(str[k]) && !is_whitespace(str[k])) {
k++;
}
// Check for space between name and bracket.
while (k < line_length && is_whitespace(str[k])) {
k++;
}
if (str[k] == '(') {
in_function_name = true;
if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
in_function_declaration = true;
}
} else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::CONST)) {
in_var_const_declaration = true;
}
// Check for lambda.
if (in_function_declaration) {
k = j - 1;
while (k > 0 && is_whitespace(str[k])) {
k--;
}
if (str[k] == ':') {
in_lambda = true;
}
}
}
}
if (!in_function_name && !in_member_variable && !in_keyword && !in_number && in_word) {
int k = j;
while (k > 0 && !is_symbol(str[k]) && !is_whitespace(str[k])) {
k--;
}
if (str[k] == '.') {
in_member_variable = true;
}
}
if (is_a_symbol) {
if (in_function_declaration || in_signal_declaration) {
is_after_func_signal_declaration = true;
}
if (in_var_const_declaration) {
is_after_var_const_declaration = true;
}
if (in_declaration_params > 0) {
switch (str[j]) {
case '(':
in_declaration_params += 1;
break;
case ')':
in_declaration_params -= 1;
break;
case '{':
in_declaration_param_dicts += 1;
break;
case '}':
in_declaration_param_dicts -= 1;
break;
}
} else if ((is_after_func_signal_declaration || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) && str[j] == '(') {
in_declaration_params = 1;
in_declaration_param_dicts = 0;
}
if (expect_type) {
switch (str[j]) {
case '[':
in_type_params += 1;
break;
case ']':
in_type_params -= 1;
break;
case ',':
if (in_type_params <= 0) {
expect_type = false;
}
break;
case ' ':
case '\t':
case '.':
break;
default:
expect_type = false;
break;
}
} else {
if (j > 0 && str[j - 1] == '-' && str[j] == '>') {
expect_type = true;
in_type_params = 0;
}
if ((is_after_var_const_declaration || (in_declaration_params == 1 && in_declaration_param_dicts == 0)) && str[j] == ':') {
expect_type = true;
in_type_params = 0;
}
}
in_function_name = false;
in_function_declaration = false;
in_signal_declaration = false;
in_var_const_declaration = false;
in_lambda = false;
in_member_variable = false;
if (!is_whitespace(str[j])) {
is_after_func_signal_declaration = false;
is_after_var_const_declaration = false;
}
}
// Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
is_binary_op = true;
} else if (j == 0 || (j > 0 && str[j - 1] != '&') || prev_is_binary_op) {
in_string_name = true;
}
} else if (in_region != -1 || is_a_symbol) {
in_string_name = false;
}
// '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret.
if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) {
in_node_path = true;
} else if (in_region != -1 || is_a_symbol) {
in_node_path = false;
}
if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) {
in_node_ref = true;
} else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%') || (is_a_digit && j > 0 && (str[j - 1] == '$' || str[j - 1] == '/' || str[j - 1] == '%'))) {
// NodeRefs can't start with digits, so point out wrong syntax immediately.
in_node_ref = false;
}
if (!in_annotation && in_region == -1 && str[j] == '@') {
in_annotation = true;
} else if (in_region != -1 || is_a_symbol) {
in_annotation = false;
}
const bool in_raw_string_prefix = in_region == -1 && str[j] == 'r' && j + 1 < line_length && (str[j + 1] == '"' || str[j + 1] == '\'');
if (in_raw_string_prefix) {
color = string_color;
} else if (in_node_ref) {
next_type = NODE_REF;
color = node_ref_color;
} else if (in_annotation) {
next_type = ANNOTATION;
color = annotation_color;
} else if (in_string_name) {
next_type = STRING_NAME;
color = string_name_color;
} else if (in_node_path) {
next_type = NODE_PATH;
color = node_path_color;
} else if (in_keyword) {
next_type = KEYWORD;
color = keyword_color;
} else if (in_signal_declaration) {
next_type = SIGNAL;
color = member_color;
} else if (in_function_name) {
next_type = FUNCTION;
if (!in_lambda && in_function_declaration) {
color = function_definition_color;
} else {
color = function_color;
}
} else if (in_number) {
next_type = NUMBER;
color = number_color;
} else if (is_a_symbol) {
next_type = SYMBOL;
color = symbol_color;
} else if (expect_type) {
next_type = TYPE;
color = type_color;
} else if (in_member_variable) {
next_type = MEMBER;
color = member_color;
} else {
next_type = IDENTIFIER;
}
if (next_type != current_type) {
if (current_type == NONE) {
current_type = next_type;
} else {
prev_type = current_type;
current_type = next_type;
// No need to store regions...
if (prev_type == REGION) {
prev_text = "";
prev_column = j;
} else {
String text = str.substr(prev_column, j - prev_column).strip_edges();
prev_column = j;
// Ignore if just whitespace.
if (!text.is_empty()) {
prev_text = text;
}
}
}
}
prev_is_char = is_char;
prev_is_digit = is_a_digit;
prev_is_binary_op = is_binary_op;
if (color != prev_color) {
prev_color = color;
highlighter_info["color"] = color;
color_map[j] = highlighter_info;
}
}
return color_map;
}
String GDScriptSyntaxHighlighter::_get_name() const {
return "GDScript";
}
PackedStringArray GDScriptSyntaxHighlighter::_get_supported_languages() const {
PackedStringArray languages;
languages.push_back("GDScript");
return languages;
}
void GDScriptSyntaxHighlighter::_update_cache() {
class_names.clear();
reserved_keywords.clear();
member_keywords.clear();
global_functions.clear();
color_regions.clear();
color_region_cache.clear();
font_color = text_edit->get_theme_color(SceneStringName(font_color));
symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color");
function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
number_color = EDITOR_GET("text_editor/theme/highlighting/number_color");
member_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
/* Engine types. */
const Color types_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
class_names[E] = types_color;
}
/* User types. */
const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
List<StringName> global_classes;
ScriptServer::get_global_class_list(&global_classes);
for (const StringName &E : global_classes) {
class_names[E] = usertype_color;
}
/* Autoloads. */
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
const ProjectSettings::AutoloadInfo &info = E.value;
if (info.is_singleton) {
class_names[info.name] = usertype_color;
}
}
const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton();
/* Core types. */
const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
List<String> core_types;
gdscript->get_core_type_words(&core_types);
for (const String &E : core_types) {
class_names[StringName(E)] = basetype_color;
}
class_names[SNAME("Variant")] = basetype_color;
class_names[SNAME("void")] = basetype_color;
// `get_core_type_words()` doesn't return primitive types.
class_names[SNAME("bool")] = basetype_color;
class_names[SNAME("int")] = basetype_color;
class_names[SNAME("float")] = basetype_color;
/* Reserved words. */
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
List<String> keyword_list;
gdscript->get_reserved_words(&keyword_list);
for (const String &E : keyword_list) {
if (gdscript->is_control_flow_keyword(E)) {
reserved_keywords[StringName(E)] = control_flow_keyword_color;
} else {
reserved_keywords[StringName(E)] = keyword_color;
}
}
// Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls.
reserved_keywords[SNAME("set")] = function_color;
reserved_keywords[SNAME("get")] = function_color;
/* Global functions. */
List<StringName> global_function_list;
GDScriptUtilityFunctions::get_function_list(&global_function_list);
Variant::get_utility_function_list(&global_function_list);
// "assert" and "preload" are not utility functions, but are global nonetheless, so insert them.
global_functions.insert(SNAME("assert"));
global_functions.insert(SNAME("preload"));
for (const StringName &E : global_function_list) {
global_functions.insert(E);
}
/* Comments */
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
List<String> comments;
gdscript->get_comment_delimiters(&comments);
for (const String &comment : comments) {
String beg = comment.get_slice(" ", 0);
String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String();
add_color_region(ColorRegion::TYPE_COMMENT, beg, end, comment_color, end.is_empty());
}
/* Doc comments */
const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
List<String> doc_comments;
gdscript->get_doc_comment_delimiters(&doc_comments);
for (const String &doc_comment : doc_comments) {
String beg = doc_comment.get_slice(" ", 0);
String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slice(" ", 1) : String();
add_color_region(ColorRegion::TYPE_COMMENT, beg, end, doc_comment_color, end.is_empty());
}
/* Code regions */
const Color code_region_color = Color(EDITOR_GET("text_editor/theme/highlighting/folded_code_region_color").operator Color(), 1.0);
add_color_region(ColorRegion::TYPE_CODE_REGION, "#region", "", code_region_color, true);
add_color_region(ColorRegion::TYPE_CODE_REGION, "#endregion", "", code_region_color, true);
/* Strings */
string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color);
add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color);
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color);
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color);
add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color, false, true);
add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color, false, true);
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color, false, true);
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color, false, true);
const Ref<Script> scr = _get_edited_resource();
if (scr.is_valid()) {
/* Member types. */
const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color");
StringName instance_base = scr->get_instance_base_type();
if (instance_base != StringName()) {
List<PropertyInfo> plist;
ClassDB::get_property_list(instance_base, &plist);
for (const PropertyInfo &E : plist) {
String prop_name = E.name;
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
continue;
}
if (prop_name.contains("/")) {
continue;
}
member_keywords[prop_name] = member_variable_color;
}
List<String> clist;
ClassDB::get_integer_constant_list(instance_base, &clist);
for (const String &E : clist) {
member_keywords[E] = member_variable_color;
}
}
}
const String text_edit_color_theme = EDITOR_GET("text_editor/theme/color_theme");
const bool godot_2_theme = text_edit_color_theme == "Godot 2";
if (godot_2_theme || EditorThemeManager::is_dark_theme()) {
function_definition_color = Color(0.4, 0.9, 1.0);
global_function_color = Color(0.64, 0.64, 0.96);
node_path_color = Color(0.72, 0.77, 0.49);
node_ref_color = Color(0.39, 0.76, 0.35);
annotation_color = Color(1.0, 0.7, 0.45);
string_name_color = Color(1.0, 0.76, 0.65);
comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.77, 0.35, 0.35);
comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.72, 0.61, 0.48);
comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.56, 0.67, 0.51);
} else {
function_definition_color = Color(0, 0.6, 0.6);
global_function_color = Color(0.36, 0.18, 0.72);
node_path_color = Color(0.18, 0.55, 0);
node_ref_color = Color(0.0, 0.5, 0);
annotation_color = Color(0.8, 0.37, 0);
string_name_color = Color(0.8, 0.56, 0.45);
comment_marker_colors[COMMENT_MARKER_CRITICAL] = Color(0.8, 0.14, 0.14);
comment_marker_colors[COMMENT_MARKER_WARNING] = Color(0.75, 0.39, 0.03);
comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
}
EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_reference_color", node_ref_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/annotation_color", annotation_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/string_name_color", string_name_color);
EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_color", comment_marker_colors[COMMENT_MARKER_CRITICAL]);
EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_color", comment_marker_colors[COMMENT_MARKER_WARNING]);
EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_color", comment_marker_colors[COMMENT_MARKER_NOTICE]);
// The list is based on <https://github.com/KDE/syntax-highlighting/blob/master/data/syntax/alert.xml>.
EDITOR_DEF("text_editor/theme/highlighting/comment_markers/critical_list", "ALERT,ATTENTION,CAUTION,CRITICAL,DANGER,SECURITY");
EDITOR_DEF("text_editor/theme/highlighting/comment_markers/warning_list", "BUG,DEPRECATED,FIXME,HACK,TASK,TBD,TODO,WARNING");
EDITOR_DEF("text_editor/theme/highlighting/comment_markers/notice_list", "INFO,NOTE,NOTICE,TEST,TESTING");
if (text_edit_color_theme == "Default" || godot_2_theme) {
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/function_definition_color",
function_definition_color,
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/global_function_color",
global_function_color,
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/node_path_color",
node_path_color,
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/node_reference_color",
node_ref_color,
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/annotation_color",
annotation_color,
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/gdscript/string_name_color",
string_name_color,
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/comment_markers/critical_color",
comment_marker_colors[COMMENT_MARKER_CRITICAL],
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/comment_markers/warning_color",
comment_marker_colors[COMMENT_MARKER_WARNING],
true);
EditorSettings::get_singleton()->set_initial_value(
"text_editor/theme/highlighting/comment_markers/notice_color",
comment_marker_colors[COMMENT_MARKER_NOTICE],
true);
}
function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color");
global_function_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/global_function_color");
node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color");
node_ref_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_reference_color");
annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color");
string_name_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/string_name_color");
type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color");
comment_marker_colors[COMMENT_MARKER_CRITICAL] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_color");
comment_marker_colors[COMMENT_MARKER_WARNING] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_color");
comment_marker_colors[COMMENT_MARKER_NOTICE] = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_color");
comment_markers.clear();
Vector<String> critical_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/critical_list").operator String().split(",", false);
for (int i = 0; i < critical_list.size(); i++) {
comment_markers[critical_list[i]] = COMMENT_MARKER_CRITICAL;
}
Vector<String> warning_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/warning_list").operator String().split(",", false);
for (int i = 0; i < warning_list.size(); i++) {
comment_markers[warning_list[i]] = COMMENT_MARKER_WARNING;
}
Vector<String> notice_list = EDITOR_GET("text_editor/theme/highlighting/comment_markers/notice_list").operator String().split(",", false);
for (int i = 0; i < notice_list.size(); i++) {
comment_markers[notice_list[i]] = COMMENT_MARKER_NOTICE;
}
}
void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only, bool p_r_prefix) {
ERR_FAIL_COND_MSG(p_start_key.is_empty(), "Color region start key cannot be empty.");
ERR_FAIL_COND_MSG(!is_symbol(p_start_key[0]), "Color region start key must start with a symbol.");
if (!p_end_key.is_empty()) {
ERR_FAIL_COND_MSG(!is_symbol(p_end_key[0]), "Color region end key must start with a symbol.");
}
int at = 0;
for (const ColorRegion &region : color_regions) {
ERR_FAIL_COND_MSG(region.start_key == p_start_key && region.r_prefix == p_r_prefix, "Color region with start key '" + p_start_key + "' already exists.");
if (p_start_key.length() < region.start_key.length()) {
at++;
} else {
break;
}
}
ColorRegion color_region;
color_region.type = p_type;
color_region.color = p_color;
color_region.start_key = p_start_key;
color_region.end_key = p_end_key;
color_region.line_only = p_line_only;
color_region.r_prefix = p_r_prefix;
color_region.is_string = p_type == ColorRegion::TYPE_STRING || p_type == ColorRegion::TYPE_MULTILINE_STRING;
color_region.is_comment = p_type == ColorRegion::TYPE_COMMENT || p_type == ColorRegion::TYPE_CODE_REGION;
color_regions.insert(at, color_region);
clear_highlighting_cache();
}
Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const {
Ref<GDScriptSyntaxHighlighter> syntax_highlighter;
syntax_highlighter.instantiate();
return syntax_highlighter;
}

View file

@ -0,0 +1,121 @@
/**************************************************************************/
/* gdscript_highlighter.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_HIGHLIGHTER_H
#define GDSCRIPT_HIGHLIGHTER_H
#include "editor/plugins/script_editor_plugin.h"
#include "scene/gui/text_edit.h"
class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter {
GDCLASS(GDScriptSyntaxHighlighter, EditorSyntaxHighlighter)
private:
struct ColorRegion {
enum Type {
TYPE_NONE,
TYPE_STRING, // `"` and `'`, optional prefix `&`, `^`, or `r`.
TYPE_MULTILINE_STRING, // `"""` and `'''`, optional prefix `r`.
TYPE_COMMENT, // `#` and `##`.
TYPE_CODE_REGION, // `#region` and `#endregion`.
};
Type type = TYPE_NONE;
Color color;
String start_key;
String end_key;
bool line_only = false;
bool r_prefix = false;
bool is_string = false; // `TYPE_STRING` or `TYPE_MULTILINE_STRING`.
bool is_comment = false; // `TYPE_COMMENT` or `TYPE_CODE_REGION`.
};
Vector<ColorRegion> color_regions;
HashMap<int, int> color_region_cache;
HashMap<StringName, Color> class_names;
HashMap<StringName, Color> reserved_keywords;
HashMap<StringName, Color> member_keywords;
HashSet<StringName> global_functions;
enum Type {
NONE,
REGION,
NODE_PATH,
NODE_REF,
ANNOTATION,
STRING_NAME,
SYMBOL,
NUMBER,
FUNCTION,
SIGNAL,
KEYWORD,
MEMBER,
IDENTIFIER,
TYPE,
};
// Colors.
Color font_color;
Color symbol_color;
Color function_color;
Color global_function_color;
Color function_definition_color;
Color built_in_type_color;
Color number_color;
Color member_color;
Color string_color;
Color node_path_color;
Color node_ref_color;
Color annotation_color;
Color string_name_color;
Color type_color;
enum CommentMarkerLevel {
COMMENT_MARKER_CRITICAL,
COMMENT_MARKER_WARNING,
COMMENT_MARKER_NOTICE,
COMMENT_MARKER_MAX,
};
Color comment_marker_colors[COMMENT_MARKER_MAX];
HashMap<String, CommentMarkerLevel> comment_markers;
void add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false, bool p_r_prefix = false);
public:
virtual void _update_cache() override;
virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override;
virtual String _get_name() const override;
virtual PackedStringArray _get_supported_languages() const override;
virtual Ref<EditorSyntaxHighlighter> _create() const override;
};
#endif // GDSCRIPT_HIGHLIGHTER_H

View file

@ -0,0 +1,384 @@
/**************************************************************************/
/* gdscript_translation_parser_plugin.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gdscript_translation_parser_plugin.h"
#include "../gdscript.h"
#include "../gdscript_analyzer.h"
#include "core/io/resource_loader.h"
void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const {
GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions);
}
Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) {
// Extract all translatable strings using the parsed tree from GDScriptParser.
// 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 = "__", tooltip_text = "__" etc.
Error err;
Ref<Resource> loaded_res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
ERR_FAIL_COND_V_MSG(err, err, "Failed to load " + p_path);
ids = r_ids;
ids_ctx_plural = r_ids_ctx_plural;
Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();
GDScriptParser parser;
err = parser.parse(source_code, p_path, false);
ERR_FAIL_COND_V_MSG(err, err, "Failed to parse GDScript with GDScriptParser.");
GDScriptAnalyzer analyzer(&parser);
err = analyzer.analyze();
ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
// Traverse through the parsed tree from GDScriptParser.
GDScriptParser::ClassNode *c = parser.get_tree();
_traverse_class(c);
return OK;
}
bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, false);
return p_expression->is_constant && (p_expression->reduced_value.get_type() == Variant::STRING || p_expression->reduced_value.get_type() == Variant::STRING_NAME);
}
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];
// Other member types can't 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:
_assess_expression(m.variable->initializer);
if (m.variable->property == GDScriptParser::VariableNode::PROP_INLINE) {
_traverse_function(m.variable->setter);
_traverse_function(m.variable->getter);
}
break;
default:
break;
}
}
}
void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) {
if (!p_func) {
return;
}
for (int i = 0; i < p_func->parameters.size(); i++) {
_assess_expression(p_func->parameters[i]->initializer);
}
_traverse_block(p_func->body);
}
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++) {
const GDScriptParser::Node *statement = statements[i];
// BREAK, BREAKPOINT, CONSTANT, CONTINUE, and PASS are skipped because they can't contain translatable strings.
switch (statement->type) {
case GDScriptParser::Node::ASSERT: {
const GDScriptParser::AssertNode *assert_node = static_cast<const GDScriptParser::AssertNode *>(statement);
_assess_expression(assert_node->condition);
_assess_expression(assert_node->message);
} break;
case GDScriptParser::Node::ASSIGNMENT: {
_assess_assignment(static_cast<const GDScriptParser::AssignmentNode *>(statement));
} break;
case GDScriptParser::Node::FOR: {
const GDScriptParser::ForNode *for_node = static_cast<const GDScriptParser::ForNode *>(statement);
_assess_expression(for_node->list);
_traverse_block(for_node->loop);
} break;
case GDScriptParser::Node::IF: {
const GDScriptParser::IfNode *if_node = static_cast<const GDScriptParser::IfNode *>(statement);
_assess_expression(if_node->condition);
_traverse_block(if_node->true_block);
_traverse_block(if_node->false_block);
} break;
case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match_node = static_cast<const GDScriptParser::MatchNode *>(statement);
_assess_expression(match_node->test);
for (int j = 0; j < match_node->branches.size(); j++) {
_traverse_block(match_node->branches[j]->guard_body);
_traverse_block(match_node->branches[j]->block);
}
} break;
case GDScriptParser::Node::RETURN: {
_assess_expression(static_cast<const GDScriptParser::ReturnNode *>(statement)->return_value);
} break;
case GDScriptParser::Node::VARIABLE: {
_assess_expression(static_cast<const GDScriptParser::VariableNode *>(statement)->initializer);
} break;
case GDScriptParser::Node::WHILE: {
const GDScriptParser::WhileNode *while_node = static_cast<const GDScriptParser::WhileNode *>(statement);
_assess_expression(while_node->condition);
_traverse_block(while_node->loop);
} break;
default: {
if (statement->is_expression()) {
_assess_expression(static_cast<const GDScriptParser::ExpressionNode *>(statement));
}
} break;
}
}
}
void GDScriptEditorTranslationParserPlugin::_assess_expression(const 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;
}
// GET_NODE, IDENTIFIER, LITERAL, PRELOAD, SELF, and TYPE are skipped because they can't contain translatable strings.
switch (p_expression->type) {
case GDScriptParser::Node::ARRAY: {
const GDScriptParser::ArrayNode *array_node = static_cast<const 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<const GDScriptParser::AssignmentNode *>(p_expression));
} break;
case GDScriptParser::Node::AWAIT: {
_assess_expression(static_cast<const GDScriptParser::AwaitNode *>(p_expression)->to_await);
} break;
case GDScriptParser::Node::BINARY_OPERATOR: {
const GDScriptParser::BinaryOpNode *binary_op_node = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression);
_assess_expression(binary_op_node->left_operand);
_assess_expression(binary_op_node->right_operand);
} break;
case GDScriptParser::Node::CALL: {
_assess_call(static_cast<const GDScriptParser::CallNode *>(p_expression));
} break;
case GDScriptParser::Node::CAST: {
_assess_expression(static_cast<const GDScriptParser::CastNode *>(p_expression)->operand);
} break;
case GDScriptParser::Node::DICTIONARY: {
const GDScriptParser::DictionaryNode *dict_node = static_cast<const 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::LAMBDA: {
_traverse_function(static_cast<const GDScriptParser::LambdaNode *>(p_expression)->function);
} break;
case GDScriptParser::Node::SUBSCRIPT: {
const GDScriptParser::SubscriptNode *subscript_node = static_cast<const GDScriptParser::SubscriptNode *>(p_expression);
_assess_expression(subscript_node->base);
if (!subscript_node->is_attribute) {
_assess_expression(subscript_node->index);
}
} break;
case GDScriptParser::Node::TERNARY_OPERATOR: {
const GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<const 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;
case GDScriptParser::Node::TYPE_TEST: {
_assess_expression(static_cast<const GDScriptParser::TypeTestNode *>(p_expression)->operand);
} break;
case GDScriptParser::Node::UNARY_OPERATOR: {
_assess_expression(static_cast<const GDScriptParser::UnaryOpNode *>(p_expression)->operand);
} break;
default: {
} break;
}
}
void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptParser::AssignmentNode *p_assignment) {
_assess_expression(p_assignment->assignee);
_assess_expression(p_assignment->assigned_value);
// 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<const GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name;
} else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee);
if (subscript->is_attribute && subscript->attribute) {
assignee_name = subscript->attribute->name;
} else if (subscript->index && _is_constant_string(subscript->index)) {
assignee_name = subscript->index->reduced_value;
}
}
if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
ids->push_back(p_assignment->assigned_value->reduced_value);
} else if (assignee_name == fd_filters) {
// Extract from `get_node("FileDialog").filters = <filter array>`.
_extract_fd_filter_array(p_assignment->assigned_value);
}
}
void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::CallNode *p_call) {
_assess_expression(p_call->callee);
for (int i = 0; i < p_call->arguments.size(); i++) {
_assess_expression(p_call->arguments[i]);
}
// 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 || function_name == atr_func) {
// Extract from `tr(id, ctx)` or `atr(id, ctx)`.
for (int i = 0; i < p_call->arguments.size(); i++) {
if (_is_constant_string(p_call->arguments[i])) {
id_ctx_plural.write[i] = p_call->arguments[i]->reduced_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 || function_name == atrn_func) {
// Extract from `tr_n(id, plural, n, ctx)` or `atr_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 (_is_constant_string(p_call->arguments[indices[i]])) {
id_ctx_plural.write[i] = p_call->arguments[indices[i]]->reduced_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)) {
if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) {
ids->push_back(p_call->arguments[0]->reduced_value);
}
} else if (second_arg_patterns.has(function_name)) {
if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) {
ids->push_back(p_call->arguments[1]->reduced_value);
}
} else if (function_name == fd_add_filter) {
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
if (!p_call->arguments.is_empty()) {
_extract_fd_filter_string(p_call->arguments[0]);
}
} else if (function_name == fd_set_filter) {
// Extract from `get_node("FileDialog").set_filters(<filter array>)`.
if (!p_call->arguments.is_empty()) {
_extract_fd_filter_array(p_call->arguments[0]);
}
}
}
void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) {
// Extract the name in "extension ; name".
if (_is_constant_string(p_expression)) {
PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true);
ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format.");
ids->push_back(arr[1].strip_edges());
}
}
void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression) {
const GDScriptParser::ArrayNode *array_node = nullptr;
if (p_expression->type == GDScriptParser::Node::ARRAY) {
// Extract from `["*.png ; PNG Images","*.gd ; GDScript Files"]` (implicit cast to `PackedStringArray`).
array_node = static_cast<const GDScriptParser::ArrayNode *>(p_expression);
} else if (p_expression->type == GDScriptParser::Node::CALL) {
// Extract from `PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])`.
const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_expression);
if (call_node->get_callee_type() == GDScriptParser::Node::IDENTIFIER && call_node->function_name == SNAME("PackedStringArray") && !call_node->arguments.is_empty() && call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) {
array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]);
}
}
if (array_node) {
for (int i = 0; i < array_node->elements.size(); i++) {
_extract_fd_filter_string(array_node->elements[i]);
}
}
}
GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() {
assignment_patterns.insert("text");
assignment_patterns.insert("placeholder_text");
assignment_patterns.insert("tooltip_text");
first_arg_patterns.insert("set_text");
first_arg_patterns.insert("set_tooltip_text");
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");
}

View file

@ -0,0 +1,78 @@
/**************************************************************************/
/* gdscript_translation_parser_plugin.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
#include "../gdscript_parser.h"
#include "core/templates/hash_set.h"
#include "editor/editor_translation_parser.h"
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
Vector<String> *ids = nullptr;
Vector<Vector<String>> *ids_ctx_plural = nullptr;
// List of patterns used for extracting translation strings.
StringName tr_func = "tr";
StringName trn_func = "tr_n";
StringName atr_func = "atr";
StringName atrn_func = "atr_n";
HashSet<StringName> assignment_patterns;
HashSet<StringName> first_arg_patterns;
HashSet<StringName> second_arg_patterns;
// FileDialog patterns.
StringName fd_add_filter = "add_filter";
StringName fd_set_filter = "set_filters";
StringName fd_filters = "filters";
static bool _is_constant_string(const GDScriptParser::ExpressionNode *p_expression);
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 _assess_expression(const GDScriptParser::ExpressionNode *p_expression);
void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment);
void _assess_call(const GDScriptParser::CallNode *p_call);
void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression);
void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression);
public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
GDScriptEditorTranslationParserPlugin();
};
#endif // GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H

View file

@ -0,0 +1,27 @@
# meta-description: Classic movement for gravity games (platformer, ...)
extends _BASE_
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()

View file

@ -0,0 +1,30 @@
# meta-description: Classic movement for gravity games (FPS, TPS, ...)
extends _BASE_
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()

View file

@ -0,0 +1,14 @@
# meta-description: Basic plugin template
@tool
extends _BASE_
func _enter_tree() -> void:
# Initialization of the plugin goes here.
pass
func _exit_tree() -> void:
# Clean-up of the plugin goes here.
pass

View file

@ -0,0 +1,10 @@
# meta-description: Basic import script template
@tool
extends _BASE_
# Called by the editor when a scene has this script set as the import script in the import tab.
func _post_import(scene: Node) -> Object:
# Modify the contents of the scene upon import.
return scene # Return the modified root node when you're done.

View file

@ -0,0 +1,8 @@
# meta-description: Basic import script template (no comments)
@tool
extends _BASE_
func _post_import(scene: Node) -> Object:
return scene

View file

@ -0,0 +1,9 @@
# meta-description: Basic editor script template
@tool
extends _BASE_
# Called when the script is executed (using File -> Run in Script Editor).
func _run() -> void:
pass

View file

@ -0,0 +1,13 @@
# meta-description: Base template for Node with default Godot cycle methods
extends _BASE_
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass

View file

@ -0,0 +1,3 @@
# meta-description: Empty template suitable for all Objects
extends _BASE_

View file

@ -0,0 +1,18 @@
# meta-description: Base template for rich text effects
@tool
# Having a class name is handy for picking the effect in the Inspector.
class_name RichText_CLASS_
extends _BASE_
# To use this effect:
# - Enable BBCode on a RichTextLabel.
# - Register this effect on the label.
# - Use [_CLASS_SNAKE_CASE_ param=2.0]hello[/_CLASS_SNAKE_CASE_] in text.
var bbcode := "_CLASS_SNAKE_CASE_"
func _process_custom_fx(char_fx: CharFXTransform) -> bool:
var param: float = char_fx.env.get("param", 1.0)
return true

View file

@ -0,0 +1,16 @@
#!/usr/bin/env python
Import("env")
import editor.template_builders as build_template_gd
env["BUILDERS"]["MakeGDTemplateBuilder"] = Builder(
action=env.Run(build_template_gd.make_templates),
suffix=".h",
src_suffix=".gd",
)
# Template files
templates_sources = Glob("*/*.gd")
env.Alias("editor_template_gd", [env.MakeGDTemplateBuilder("templates.gen.h", templates_sources)])

View file

@ -0,0 +1,51 @@
# meta-description: Visual shader's node plugin template
@tool
# Having a class name is required for a custom node.
class_name VisualShaderNode_CLASS_
extends _BASE_
func _get_name() -> String:
return "_CLASS_"
func _get_category() -> String:
return ""
func _get_description() -> String:
return ""
func _get_return_icon_type() -> PortType:
return PORT_TYPE_SCALAR
func _get_input_port_count() -> int:
return 0
func _get_input_port_name(port: int) -> String:
return ""
func _get_input_port_type(port: int) -> PortType:
return PORT_TYPE_SCALAR
func _get_output_port_count() -> int:
return 1
func _get_output_port_name(port: int) -> String:
return "result"
func _get_output_port_type(port: int) -> PortType:
return PORT_TYPE_SCALAR
func _get_code(input_vars: Array[String], output_vars: Array[String],
mode: Shader.Mode, type: VisualShader.Type) -> String:
return output_vars[0] + " = 0.0;"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,651 @@
/**************************************************************************/
/* gdscript.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_H
#define GDSCRIPT_H
#include "gdscript_function.h"
#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/object/script_language.h"
#include "core/templates/rb_set.h"
class GDScriptNativeClass : public RefCounted {
GDCLASS(GDScriptNativeClass, RefCounted);
StringName name;
protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
static void _bind_methods();
public:
_FORCE_INLINE_ const StringName &get_name() const { return name; }
Variant _new();
Object *instantiate();
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
GDScriptNativeClass(const StringName &p_name);
};
class GDScript : public Script {
GDCLASS(GDScript, Script);
bool tool = false;
bool valid = false;
bool reloading = false;
struct MemberInfo {
int index = 0;
StringName setter;
StringName getter;
GDScriptDataType data_type;
PropertyInfo property_info;
};
struct ClearData {
RBSet<GDScriptFunction *> functions;
RBSet<Ref<Script>> scripts;
void clear() {
functions.clear();
scripts.clear();
}
};
friend class GDScriptInstance;
friend class GDScriptFunction;
friend class GDScriptAnalyzer;
friend class GDScriptCompiler;
friend class GDScriptDocGen;
friend class GDScriptLambdaCallable;
friend class GDScriptLambdaSelfCallable;
friend class GDScriptLanguage;
friend struct GDScriptUtilityFunctionsDefinitions;
Ref<GDScriptNativeClass> native;
Ref<GDScript> base;
GDScript *_base = nullptr; //fast pointer access
GDScript *_owner = nullptr; //for subclasses
// Members are just indices to the instantiated script.
HashMap<StringName, MemberInfo> member_indices; // Includes member info of all base GDScript classes.
HashSet<StringName> members; // Only members of the current class.
// Only static variables of the current class.
HashMap<StringName, MemberInfo> static_variables_indices;
Vector<Variant> static_variables; // Static variable values.
HashMap<StringName, Variant> constants;
HashMap<StringName, GDScriptFunction *> member_functions;
HashMap<StringName, Ref<GDScript>> subclasses;
HashMap<StringName, MethodInfo> _signals;
Dictionary rpc_config;
struct LambdaInfo {
int capture_count;
bool use_self;
};
HashMap<GDScriptFunction *, LambdaInfo> lambda_info;
public:
class UpdatableFuncPtr {
friend class GDScript;
GDScriptFunction *ptr = nullptr;
GDScript *script = nullptr;
List<UpdatableFuncPtr *>::Element *list_element = nullptr;
public:
GDScriptFunction *operator->() const { return ptr; }
operator GDScriptFunction *() const { return ptr; }
UpdatableFuncPtr(GDScriptFunction *p_function);
~UpdatableFuncPtr();
};
private:
// List is used here because a ptr to elements are stored, so the memory locations need to be stable
List<UpdatableFuncPtr *> func_ptrs_to_update;
Mutex func_ptrs_to_update_mutex;
void _recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const;
#ifdef TOOLS_ENABLED
// For static data storage during hot-reloading.
HashMap<StringName, MemberInfo> old_static_variables_indices;
Vector<Variant> old_static_variables;
void _save_old_static_data();
void _restore_old_static_data();
HashMap<StringName, int> member_lines;
HashMap<StringName, Variant> member_default_values;
List<PropertyInfo> members_cache;
HashMap<StringName, Variant> member_default_values_cache;
Ref<GDScript> base_cache;
HashSet<ObjectID> inheriters_cache;
bool source_changed_cache = false;
bool placeholder_fallback_enabled = false;
void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames);
DocData::ClassDoc doc;
Vector<DocData::ClassDoc> docs;
void _clear_doc();
void _add_doc(const DocData::ClassDoc &p_inner_class);
#endif
GDScriptFunction *implicit_initializer = nullptr;
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
GDScriptFunction *implicit_ready = nullptr;
GDScriptFunction *static_initializer = nullptr;
Error _static_init();
void _static_default_init(); // Initialize static variables with default values based on their types.
int subclass_count = 0;
RBSet<Object *> instances;
bool destructing = false;
bool clearing = false;
//exported members
String source;
Vector<uint8_t> binary_tokens;
String path;
bool path_valid = false; // False if using default path.
StringName local_name; // Inner class identifier or `class_name`.
StringName global_name; // `class_name`.
String fully_qualified_name;
String simplified_icon_path;
SelfList<GDScript> script_list;
SelfList<GDScriptFunctionState>::List pending_func_states;
GDScriptFunction *_super_constructor(GDScript *p_script);
void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error);
GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error);
String _get_debug_path() const;
#ifdef TOOLS_ENABLED
HashSet<PlaceHolderScriptInstance *> placeholders;
//void _update_placeholder(PlaceHolderScriptInstance *p_placeholder);
virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override;
void _update_exports_down(bool p_base_exports_changed);
#endif
#ifdef DEBUG_ENABLED
HashMap<ObjectID, List<Pair<StringName, Variant>>> pending_reload_state;
#endif
bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr, bool p_base_exports_changed = false);
void _save_orphaned_subclasses(GDScript::ClearData *p_clear_data);
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;
GDScript *_get_gdscript_from_variant(const Variant &p_variant);
void _collect_function_dependencies(GDScriptFunction *p_func, RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
void _collect_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
bool _set(const StringName &p_name, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_properties) const;
Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
static void _bind_methods();
public:
#ifdef DEBUG_ENABLED
static String debug_get_script_name(const Ref<Script> &p_script);
#endif
static String canonicalize_path(const String &p_path);
_FORCE_INLINE_ static bool is_canonically_equal_paths(const String &p_path_a, const String &p_path_b) {
return canonicalize_path(p_path_a) == canonicalize_path(p_path_b);
}
_FORCE_INLINE_ StringName get_local_name() const { return local_name; }
void clear(GDScript::ClearData *p_clear_data = nullptr);
virtual bool is_valid() const override { return valid; }
virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes.
bool inherits_script(const Ref<Script> &p_script) const override;
GDScript *find_class(const String &p_qualified_name);
bool has_class(const GDScript *p_script);
GDScript *get_root_script();
bool is_root_script() const { return _owner == nullptr; }
String get_fully_qualified_name() const { return fully_qualified_name; }
const HashMap<StringName, Ref<GDScript>> &get_subclasses() const { return subclasses; }
const HashMap<StringName, Variant> &get_constants() const { return constants; }
const HashSet<StringName> &get_members() const { return members; }
const GDScriptDataType &get_member_type(const StringName &p_member) const {
CRASH_COND(!member_indices.has(p_member));
return member_indices[p_member].data_type;
}
const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; }
const Ref<GDScriptNativeClass> &get_native() const { return native; }
RBSet<GDScript *> get_dependencies();
HashMap<GDScript *, RBSet<GDScript *>> get_all_dependencies();
RBSet<GDScript *> get_must_clear_dependencies();
virtual bool has_script_signal(const StringName &p_signal) const override;
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override;
bool is_tool() const override { return tool; }
Ref<GDScript> get_base() const;
const HashMap<StringName, MemberInfo> &debug_get_member_indices() const { return member_indices; }
const HashMap<StringName, GDScriptFunction *> &debug_get_member_functions() const; //this is debug only
StringName debug_get_member_by_index(int p_idx) const;
StringName debug_get_static_var_by_index(int p_idx) const;
Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
virtual bool can_instantiate() const override;
virtual Ref<Script> get_base_script() const override;
virtual StringName get_global_name() const override;
virtual StringName get_instance_base_type() const override; // this may not work in all scripts, will return empty if so
virtual ScriptInstance *instance_create(Object *p_this) override;
virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override;
virtual bool instance_has(const Object *p_this) const override;
virtual bool has_source_code() const override;
virtual String get_source_code() const override;
virtual void set_source_code(const String &p_code) override;
virtual void update_exports() override;
#ifdef TOOLS_ENABLED
virtual Vector<DocData::ClassDoc> get_documentation() const override {
return docs;
}
virtual String get_class_icon_path() const override;
#endif // TOOLS_ENABLED
virtual Error reload(bool p_keep_state = false) override;
virtual void set_path(const String &p_path, bool p_take_over = false) override;
String get_script_path() const;
Error load_source_code(const String &p_path);
void set_binary_tokens_source(const Vector<uint8_t> &p_binary_tokens);
const Vector<uint8_t> &get_binary_tokens_source() const;
Vector<uint8_t> get_as_binary_tokens() const;
bool get_property_default_value(const StringName &p_property, Variant &r_value) const override;
virtual void get_script_method_list(List<MethodInfo> *p_list) const override;
virtual bool has_method(const StringName &p_method) const override;
virtual bool has_static_method(const StringName &p_method) const override;
virtual int get_script_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const override;
virtual MethodInfo get_method_info(const StringName &p_method) const override;
virtual void get_script_property_list(List<PropertyInfo> *p_list) const override;
virtual ScriptLanguage *get_language() const override;
virtual int get_member_line(const StringName &p_member) const override {
#ifdef TOOLS_ENABLED
if (member_lines.has(p_member)) {
return member_lines[p_member];
}
#endif
return -1;
}
virtual void get_constants(HashMap<StringName, Variant> *p_constants) override;
virtual void get_members(HashSet<StringName> *p_members) override;
virtual const Variant get_rpc_config() const override;
void unload_static() const;
#ifdef TOOLS_ENABLED
virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
#endif
GDScript();
~GDScript();
};
class GDScriptInstance : public ScriptInstance {
friend class GDScript;
friend class GDScriptFunction;
friend class GDScriptLambdaCallable;
friend class GDScriptLambdaSelfCallable;
friend class GDScriptCompiler;
friend class GDScriptCache;
friend struct GDScriptUtilityFunctionsDefinitions;
ObjectID owner_id;
Object *owner = nullptr;
Ref<GDScript> script;
#ifdef DEBUG_ENABLED
HashMap<StringName, int> member_indices_cache; //used only for hot script reloading
#endif
Vector<Variant> members;
bool base_ref_counted;
SelfList<GDScriptFunctionState>::List pending_func_states;
void _call_implicit_ready_recursively(GDScript *p_script);
public:
virtual Object *get_owner() { return owner; }
virtual bool set(const StringName &p_name, const Variant &p_value);
virtual bool get(const StringName &p_name, Variant &r_ret) const;
virtual void get_property_list(List<PropertyInfo> *p_properties) const;
virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const;
virtual void validate_property(PropertyInfo &p_property) const;
virtual bool property_can_revert(const StringName &p_name) const;
virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const;
virtual void get_method_list(List<MethodInfo> *p_list) const;
virtual bool has_method(const StringName &p_method) const;
virtual int get_method_argument_count(const StringName &p_method, bool *r_is_valid = nullptr) const;
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }
virtual void notification(int p_notification, bool p_reversed = false);
String to_string(bool *r_valid);
virtual Ref<Script> get_script() const;
virtual ScriptLanguage *get_language();
void set_path(const String &p_path);
void reload_members();
virtual const Variant get_rpc_config() const;
GDScriptInstance();
~GDScriptInstance();
};
class GDScriptLanguage : public ScriptLanguage {
friend class GDScriptFunctionState;
static GDScriptLanguage *singleton;
bool finishing = false;
Variant *_global_array = nullptr;
Vector<Variant> global_array;
HashMap<StringName, int> globals;
HashMap<StringName, Variant> named_globals;
struct CallLevel {
Variant *stack = nullptr;
GDScriptFunction *function = nullptr;
GDScriptInstance *instance = nullptr;
int *ip = nullptr;
int *line = nullptr;
};
static thread_local int _debug_parse_err_line;
static thread_local String _debug_parse_err_file;
static thread_local String _debug_error;
struct CallStack {
CallLevel *levels = nullptr;
int stack_pos = 0;
void free() {
if (levels) {
memdelete(levels);
levels = nullptr;
}
}
~CallStack() {
free();
}
};
static thread_local CallStack _call_stack;
int _debug_max_call_stack = 0;
void _add_global(const StringName &p_name, const Variant &p_value);
friend class GDScriptInstance;
Mutex mutex;
friend class GDScript;
SelfList<GDScript>::List script_list;
friend class GDScriptFunction;
SelfList<GDScriptFunction>::List function_list;
bool profiling;
bool profile_native_calls;
uint64_t script_frame_time;
HashMap<String, ObjectID> orphan_subclasses;
public:
int calls;
bool debug_break(const String &p_error, bool p_allow_continue = true);
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
_FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) {
if (unlikely(_call_stack.levels == nullptr)) {
_call_stack.levels = memnew_arr(CallLevel, _debug_max_call_stack + 1);
}
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1);
}
if (_call_stack.stack_pos >= _debug_max_call_stack) {
//stack overflow
_debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack);
EngineDebugger::get_script_debugger()->debug(this);
return;
}
_call_stack.levels[_call_stack.stack_pos].stack = p_stack;
_call_stack.levels[_call_stack.stack_pos].instance = p_instance;
_call_stack.levels[_call_stack.stack_pos].function = p_function;
_call_stack.levels[_call_stack.stack_pos].ip = p_ip;
_call_stack.levels[_call_stack.stack_pos].line = p_line;
_call_stack.stack_pos++;
}
_FORCE_INLINE_ void exit_function() {
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1);
}
if (_call_stack.stack_pos == 0) {
_debug_error = "Stack Underflow (Engine Bug)";
EngineDebugger::get_script_debugger()->debug(this);
return;
}
_call_stack.stack_pos--;
}
virtual Vector<StackInfo> debug_get_current_stack_info() override {
Vector<StackInfo> csi;
csi.resize(_call_stack.stack_pos);
for (int i = 0; i < _call_stack.stack_pos; i++) {
csi.write[_call_stack.stack_pos - i - 1].line = _call_stack.levels[i].line ? *_call_stack.levels[i].line : 0;
if (_call_stack.levels[i].function) {
csi.write[_call_stack.stack_pos - i - 1].func = _call_stack.levels[i].function->get_name();
csi.write[_call_stack.stack_pos - i - 1].file = _call_stack.levels[i].function->get_script()->get_script_path();
}
}
return csi;
}
struct {
StringName _init;
StringName _static_init;
StringName _notification;
StringName _set;
StringName _get;
StringName _get_property_list;
StringName _validate_property;
StringName _property_can_revert;
StringName _property_get_revert;
StringName _script_source;
} strings;
_FORCE_INLINE_ int get_global_array_size() const { return global_array.size(); }
_FORCE_INLINE_ Variant *get_global_array() { return _global_array; }
_FORCE_INLINE_ const HashMap<StringName, int> &get_global_map() const { return globals; }
_FORCE_INLINE_ const HashMap<StringName, Variant> &get_named_globals_map() const { return named_globals; }
// These two functions should be used when behavior needs to be consistent between in-editor and running the scene
bool has_any_global_constant(const StringName &p_name) { return named_globals.has(p_name) || globals.has(p_name); }
Variant get_any_global_constant(const StringName &p_name);
_FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; }
virtual String get_name() const override;
/* LANGUAGE FUNCTIONS */
virtual void init() override;
virtual String get_type() const override;
virtual String get_extension() const override;
virtual void finish() override;
/* EDITOR FUNCTIONS */
virtual void get_reserved_words(List<String> *p_words) const override;
virtual bool is_control_flow_keyword(const String &p_keywords) const override;
virtual void get_comment_delimiters(List<String> *p_delimiters) const override;
virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const override;
virtual void get_string_delimiters(List<String> *p_delimiters) const override;
virtual bool is_using_templates() override;
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override;
virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override;
virtual Script *create_script() const override;
#ifndef DISABLE_DEPRECATED
virtual bool has_named_classes() const override { return false; }
#endif
virtual bool supports_builtin_mode() const override;
virtual bool supports_documentation() const override;
virtual bool can_inherit_from_file() const override { return true; }
virtual int find_function(const String &p_function, const String &p_code) const override;
virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) override;
#ifdef TOOLS_ENABLED
virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) override;
#endif
virtual String _get_indentation() const;
virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override;
virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) override;
virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) override;
virtual void remove_named_global_constant(const StringName &p_name) override;
/* DEBUGGER FUNCTIONS */
virtual String debug_get_error() const override;
virtual int debug_get_stack_level_count() const override;
virtual int debug_get_stack_level_line(int p_level) const override;
virtual String debug_get_stack_level_function(int p_level) const override;
virtual String debug_get_stack_level_source(int p_level) const override;
virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
virtual ScriptInstance *debug_get_stack_level_instance(int p_level) override;
virtual void debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) override;
virtual void reload_all_scripts() override;
virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) override;
virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
virtual void frame() override;
virtual void get_public_functions(List<MethodInfo> *p_functions) const override;
virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const override;
virtual void get_public_annotations(List<MethodInfo> *p_annotations) const override;
virtual void profiling_start() override;
virtual void profiling_stop() override;
virtual void profiling_set_save_native_calls(bool p_enable) override;
void profiling_collate_native_call_data(bool p_accumulated);
virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override;
virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override;
/* LOADER FUNCTIONS */
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
/* GLOBAL CLASSES */
virtual bool handles_global_class_type(const String &p_type) const override;
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override;
void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);
Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name);
GDScriptLanguage();
~GDScriptLanguage();
};
class ResourceFormatLoaderGDScript : public ResourceFormatLoader {
public:
virtual Ref<Resource> 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) override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual bool handles_type(const String &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override;
};
class ResourceFormatSaverGDScript : public ResourceFormatSaver {
public:
virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override;
virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override;
virtual bool recognize(const Ref<Resource> &p_resource) const override;
};
#endif // GDSCRIPT_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,169 @@
/**************************************************************************/
/* gdscript_analyzer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GDSCRIPT_ANALYZER_H
#define GDSCRIPT_ANALYZER_H
#include "gdscript_cache.h"
#include "gdscript_parser.h"
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/templates/hash_set.h"
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
template <typename Fn>
class Finally {
Fn fn;
public:
Finally(Fn p_fn) :
fn(p_fn) {}
~Finally() {
fn();
}
};
const GDScriptParser::EnumNode *current_enum = nullptr;
GDScriptParser::LambdaNode *current_lambda = nullptr;
List<GDScriptParser::LambdaNode *> pending_body_resolution_lambdas;
HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>> external_class_parser_cache;
bool static_context = false;
// Tests for detecting invalid overloading of script members
static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member);
static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string);
Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string);
Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node);
void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list, GDScriptParser::Node *p_source);
Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive);
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement);
void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation);
void resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_interface(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive);
void resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr);
void resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive);
void resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source = nullptr, bool p_is_lambda = false);
void resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda = false);
void resolve_node(GDScriptParser::Node *p_node, bool p_is_root = true);
void resolve_suite(GDScriptParser::SuiteNode *p_suite);
void resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind);
void resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local);
void resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local);
void resolve_parameter(GDScriptParser::ParameterNode *p_parameter);
void resolve_if(GDScriptParser::IfNode *p_if);
void resolve_for(GDScriptParser::ForNode *p_for);
void resolve_while(GDScriptParser::WhileNode *p_while);
void resolve_assert(GDScriptParser::AssertNode *p_assert);
void resolve_match(GDScriptParser::MatchNode *p_match);
void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test);
void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test);
void resolve_return(GDScriptParser::ReturnNode *p_return);
// Reduction functions.
void reduce_expression(GDScriptParser::ExpressionNode *p_expression, bool p_is_root = false);
void reduce_array(GDScriptParser::ArrayNode *p_array);
void reduce_assignment(GDScriptParser::AssignmentNode *p_assignment);
void reduce_await(GDScriptParser::AwaitNode *p_await);
void reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op);
void reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await = false, bool p_is_root = false);
void reduce_cast(GDScriptParser::CastNode *p_cast);
void reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary);
void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node);
void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false);
void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr);
void reduce_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);
void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript, bool p_can_be_pseudo_type = false);
void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false);
void reduce_type_test(GDScriptParser::TypeTestNode *p_type_test);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
Variant make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced);
Variant make_array_reduced_value(GDScriptParser::ArrayNode *p_array, bool &is_reduced);
Variant make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced);
Variant make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced);
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
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, BitField<MethodFlags> &r_method_flags);
void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source);
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
void downgrade_node_type_source(GDScriptParser::Node *p_node);
void mark_lambda_use_self();
void resolve_pending_lambda_bodies();
bool class_exists(const StringName &p_class) const;
void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype);
Ref<GDScriptParserRef> ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source);
Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser);
Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser);
Ref<GDScript> get_depended_shallow_script(const String &p_path, Error &r_error);
#ifdef DEBUG_ENABLED
void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope);
#endif
public:
Error resolve_inheritance();
Error resolve_interface();
Error resolve_body();
Error resolve_dependencies();
Error analyze();
Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable);
static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptAnalyzer(GDScriptParser *p_parser);
};
#endif // GDSCRIPT_ANALYZER_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,555 @@
/**************************************************************************/
/* gdscript_byte_codegen.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_H
#define GDSCRIPT_BYTE_CODEGEN_H
#include "gdscript_codegen.h"
#include "gdscript_function.h"
#include "gdscript_utility_functions.h"
class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
struct StackSlot {
Variant::Type type = Variant::NIL;
bool can_contain_object = true;
Vector<int> bytecode_indices;
StackSlot() = default;
StackSlot(Variant::Type p_type, bool p_can_contain_object) :
type(p_type), can_contain_object(p_can_contain_object) {}
};
struct CallTarget {
Address target;
bool is_new_temporary = false;
GDScriptByteCodeGenerator *codegen = nullptr;
#ifdef DEV_ENABLED
bool cleaned = false;
#endif
void cleanup() {
DEV_ASSERT(!cleaned);
if (is_new_temporary) {
codegen->pop_temporary();
}
#ifdef DEV_ENABLED
cleaned = true;
#endif
}
CallTarget(Address p_target, bool p_is_new_temporary, GDScriptByteCodeGenerator *p_codegen) :
target(p_target),
is_new_temporary(p_is_new_temporary),
codegen(p_codegen) {}
~CallTarget() { DEV_ASSERT(cleaned); }
CallTarget(const CallTarget &) = delete;
CallTarget &operator=(CallTarget &) = delete;
};
bool ended = false;
GDScriptFunction *function = nullptr;
bool debug_stack = false;
Vector<int> opcodes;
List<RBMap<StringName, int>> stack_id_stack;
RBMap<StringName, int> stack_identifiers;
List<int> stack_identifiers_counts;
RBMap<StringName, int> local_constants;
Vector<StackSlot> locals;
HashSet<int> dirty_locals;
Vector<StackSlot> temporaries;
List<int> used_temporaries;
HashSet<int> temporaries_pending_clear;
RBMap<Variant::Type, List<int>> temporaries_pool;
List<GDScriptFunction::StackDebug> stack_debug;
List<RBMap<StringName, int>> block_identifier_stack;
RBMap<StringName, int> block_identifiers;
int max_locals = 0;
int current_line = 0;
int instr_args_max = 0;
#ifdef DEBUG_ENABLED
List<int> temp_stack;
#endif
HashMap<Variant, int, VariantHasher, VariantComparator> constant_map;
RBMap<StringName, int> name_map;
#ifdef TOOLS_ENABLED
Vector<StringName> named_globals;
#endif
RBMap<Variant::ValidatedOperatorEvaluator, int> operator_func_map;
RBMap<Variant::ValidatedSetter, int> setters_map;
RBMap<Variant::ValidatedGetter, int> getters_map;
RBMap<Variant::ValidatedKeyedSetter, int> keyed_setters_map;
RBMap<Variant::ValidatedKeyedGetter, int> keyed_getters_map;
RBMap<Variant::ValidatedIndexedSetter, int> indexed_setters_map;
RBMap<Variant::ValidatedIndexedGetter, int> indexed_getters_map;
RBMap<Variant::ValidatedBuiltInMethod, int> builtin_method_map;
RBMap<Variant::ValidatedConstructor, int> constructors_map;
RBMap<Variant::ValidatedUtilityFunction, int> utilities_map;
RBMap<GDScriptUtilityFunctions::FunctionPtr, int> gds_utilities_map;
RBMap<MethodBind *, int> method_bind_map;
RBMap<GDScriptFunction *, int> lambdas_map;
#ifdef DEBUG_ENABLED
// Keep method and property names for pointer and validated operations.
// Used when disassembling the bytecode.
Vector<String> operator_names;
Vector<String> setter_names;
Vector<String> getter_names;
Vector<String> builtin_methods_names;
Vector<String> constructors_names;
Vector<String> utilities_names;
Vector<String> gds_utilities_names;
void add_debug_name(Vector<String> &vector, int index, const String &name) {
if (index >= vector.size()) {
vector.resize(index + 1);
}
vector.write[index] = name;
}
#endif
// Lists since these can be nested.
List<int> if_jmp_addrs;
List<int> for_jmp_addrs;
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;
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) {
RBMap<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
for (int i = current_locals; i < locals.size(); i++) {
dirty_locals.insert(i + GDScriptFunction::FIXED_ADDRESSES_MAX);
}
locals.resize(current_locals);
if (debug_stack) {
for (const KeyValue<StringName, int> &E : block_identifiers) {
GDScriptFunction::StackDebug sd;
sd.added = false;
sd.identifier = E.key;
sd.line = current_line;
sd.pos = E.value;
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;
}
CallTarget get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL);
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_opcode(GDScriptFunction::Opcode p_code) {
opcodes.push_back(p_code);
}
void append_opcode_and_argcount(GDScriptFunction::Opcode p_code, int p_argument_count) {
opcodes.push_back(p_code);
opcodes.push_back(p_argument_count);
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 clear_temporaries() override;
virtual void clear_address(const Address &p_address) override;
virtual bool is_local_dirty(const Address &p_address) const 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, Variant p_rpc_config, 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 GDScriptDataType &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_set_static_variable(const Address &p_value, const Address &p_class, int p_index) override;
virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) override;
virtual void write_assign(const Address &p_target, const Address &p_source) override;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override;
virtual void write_assign_null(const Address &p_target) 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, bool p_use_conversion) override;
virtual void write_store_global(const Address &p_dst, int p_global_index) 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;
void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, bool p_is_static, const Vector<Address> &p_arguments);
virtual void write_call_gdscript_utility(const Address &p_target, const StringName &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_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_native_static_validated(const Address &p_target, MethodBind *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_method_bind_validated(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, bool p_use_self) 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 write_jump_if_shared(const Address &p_value) override;
virtual void write_end_jump_if_shared() override;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) override;
virtual void write_for_assignment(const Address &p_list) override;
virtual void write_for(const Address &p_variable, bool p_use_conversion) 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 write_break() override;
virtual void write_continue() 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_H

View file

@ -0,0 +1,487 @@
/**************************************************************************/
/* gdscript_cache.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gdscript_cache.h"
#include "gdscript.h"
#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
#include "core/io/file_access.h"
#include "core/templates/vector.h"
GDScriptParserRef::Status GDScriptParserRef::get_status() const {
return status;
}
String GDScriptParserRef::get_path() const {
return path;
}
uint32_t GDScriptParserRef::get_source_hash() const {
return source_hash;
}
GDScriptParser *GDScriptParserRef::get_parser() {
if (parser == nullptr) {
parser = memnew(GDScriptParser);
}
return parser;
}
GDScriptAnalyzer *GDScriptParserRef::get_analyzer() {
if (analyzer == nullptr) {
analyzer = memnew(GDScriptAnalyzer(get_parser()));
}
return analyzer;
}
Error GDScriptParserRef::raise_status(Status p_new_status) {
ERR_FAIL_COND_V(clearing, ERR_BUG);
ERR_FAIL_COND_V(parser == nullptr && status != EMPTY, ERR_BUG);
while (result == OK && p_new_status > status) {
switch (status) {
case EMPTY: {
// Calling parse will clear the parser, which can destruct another GDScriptParserRef which can clear the last reference to the script with this path, calling remove_script, which clears this GDScriptParserRef.
// It's ok if its the first thing done here.
get_parser()->clear();
status = PARSED;
String remapped_path = ResourceLoader::path_remap(path);
if (remapped_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> tokens = GDScriptCache::get_binary_tokens(remapped_path);
source_hash = hash_djb2_buffer(tokens.ptr(), tokens.size());
result = get_parser()->parse_binary(tokens, path);
} else {
String source = GDScriptCache::get_source_code(remapped_path);
source_hash = source.hash();
result = get_parser()->parse(source, path, false);
}
} break;
case PARSED: {
status = INHERITANCE_SOLVED;
result = get_analyzer()->resolve_inheritance();
} break;
case INHERITANCE_SOLVED: {
status = INTERFACE_SOLVED;
result = get_analyzer()->resolve_interface();
} break;
case INTERFACE_SOLVED: {
status = FULLY_SOLVED;
result = get_analyzer()->resolve_body();
} break;
case FULLY_SOLVED: {
return result;
}
}
}
return result;
}
void GDScriptParserRef::clear() {
if (clearing) {
return;
}
clearing = true;
GDScriptParser *lparser = parser;
GDScriptAnalyzer *lanalyzer = analyzer;
parser = nullptr;
analyzer = nullptr;
status = EMPTY;
result = OK;
source_hash = 0;
clearing = false;
if (lanalyzer != nullptr) {
memdelete(lanalyzer);
}
if (lparser != nullptr) {
memdelete(lparser);
}
}
GDScriptParserRef::~GDScriptParserRef() {
clear();
if (!abandoned) {
MutexLock lock(GDScriptCache::singleton->mutex);
GDScriptCache::singleton->parser_map.erase(path);
}
}
GDScriptCache *GDScriptCache::singleton = nullptr;
void GDScriptCache::move_script(const String &p_from, const String &p_to) {
if (singleton == nullptr || p_from == p_to) {
return;
}
MutexLock lock(singleton->mutex);
if (singleton->cleared) {
return;
}
remove_parser(p_from);
if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) {
singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from];
}
singleton->shallow_gdscript_cache.erase(p_from);
if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) {
singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from];
}
singleton->full_gdscript_cache.erase(p_from);
}
void GDScriptCache::remove_script(const String &p_path) {
if (singleton == nullptr) {
return;
}
MutexLock lock(singleton->mutex);
if (singleton->cleared) {
return;
}
if (HashMap<String, Vector<ObjectID>>::Iterator E = singleton->abandoned_parser_map.find(p_path)) {
for (ObjectID parser_ref_id : E->value) {
Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) };
if (parser_ref.is_valid()) {
parser_ref->clear();
}
}
}
singleton->abandoned_parser_map.erase(p_path);
if (singleton->parser_map.has(p_path)) {
singleton->parser_map[p_path]->clear();
}
remove_parser(p_path);
singleton->dependencies.erase(p_path);
singleton->shallow_gdscript_cache.erase(p_path);
singleton->full_gdscript_cache.erase(p_path);
}
Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
Ref<GDScriptParserRef> ref;
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
singleton->parser_inverse_dependencies[p_path].insert(p_owner);
}
if (singleton->parser_map.has(p_path)) {
ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]);
if (ref.is_null()) {
r_error = ERR_INVALID_DATA;
return ref;
}
} else {
String remapped_path = ResourceLoader::path_remap(p_path);
if (!FileAccess::exists(remapped_path)) {
r_error = ERR_FILE_NOT_FOUND;
return ref;
}
ref.instantiate();
ref->path = p_path;
singleton->parser_map[p_path] = ref.ptr();
}
r_error = ref->raise_status(p_status);
return ref;
}
bool GDScriptCache::has_parser(const String &p_path) {
MutexLock lock(singleton->mutex);
return singleton->parser_map.has(p_path);
}
void GDScriptCache::remove_parser(const String &p_path) {
MutexLock lock(singleton->mutex);
if (singleton->parser_map.has(p_path)) {
GDScriptParserRef *parser_ref = singleton->parser_map[p_path];
parser_ref->abandoned = true;
singleton->abandoned_parser_map[p_path].push_back(parser_ref->get_instance_id());
}
// Can't clear the parser because some other parser might be currently using it in the chain of calls.
singleton->parser_map.erase(p_path);
// Have to copy while iterating, because parser_inverse_dependencies is modified.
HashSet<String> ideps = singleton->parser_inverse_dependencies[p_path];
singleton->parser_inverse_dependencies.erase(p_path);
for (String idep_path : ideps) {
remove_parser(idep_path);
}
}
String GDScriptCache::get_source_code(const String &p_path) {
Vector<uint8_t> source_file;
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err, "");
uint64_t len = f->get_length();
source_file.resize(len + 1);
uint64_t r = f->get_buffer(source_file.ptrw(), len);
ERR_FAIL_COND_V(r != len, "");
source_file.write[len] = 0;
String source;
if (source.parse_utf8((const char *)source_file.ptr()) != OK) {
ERR_FAIL_V_MSG("", "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
}
return source;
}
Vector<uint8_t> GDScriptCache::get_binary_tokens(const String &p_path) {
Vector<uint8_t> buffer;
Error err = OK;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V_MSG(err != OK, buffer, "Failed to open binary GDScript file '" + p_path + "'.");
uint64_t len = f->get_length();
buffer.resize(len);
uint64_t read = f->get_buffer(buffer.ptrw(), buffer.size());
ERR_FAIL_COND_V_MSG(read != len, Vector<uint8_t>(), "Failed to read binary GDScript file '" + p_path + "'.");
return buffer;
}
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
}
if (singleton->full_gdscript_cache.has(p_path)) {
return singleton->full_gdscript_cache[p_path];
}
if (singleton->shallow_gdscript_cache.has(p_path)) {
return singleton->shallow_gdscript_cache[p_path];
}
String remapped_path = ResourceLoader::path_remap(p_path);
Ref<GDScript> script;
script.instantiate();
script->set_path(p_path, true);
if (remapped_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
r_error = ERR_FILE_CANT_READ;
}
script->set_binary_tokens_source(buffer);
} else {
r_error = script->load_source_code(remapped_path);
}
if (r_error) {
return Ref<GDScript>(); // Returns null and does not cache when the script fails to load.
}
Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error);
if (r_error == OK) {
GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true);
}
singleton->shallow_gdscript_cache[p_path] = script;
return script;
}
Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) {
MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
}
Ref<GDScript> script;
r_error = OK;
if (singleton->full_gdscript_cache.has(p_path)) {
script = singleton->full_gdscript_cache[p_path];
if (!p_update_from_disk) {
return script;
}
}
if (script.is_null()) {
script = get_shallow_script(p_path, r_error);
// Only exit early if script failed to load, otherwise let reload report errors.
if (script.is_null()) {
return script;
}
}
if (p_update_from_disk) {
if (p_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(p_path);
if (buffer.is_empty()) {
r_error = ERR_FILE_CANT_READ;
return script;
}
script->set_binary_tokens_source(buffer);
} else {
r_error = script->load_source_code(p_path);
if (r_error) {
return script;
}
}
}
// Allowing lifting the lock might cause a script to be reloaded multiple times,
// which, as a last resort deadlock prevention strategy, is a good tradeoff.
uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&singleton->mutex);
r_error = script->reload(true);
WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id);
if (r_error) {
return script;
}
singleton->full_gdscript_cache[p_path] = script;
singleton->shallow_gdscript_cache.erase(p_path);
return script;
}
Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) {
MutexLock lock(singleton->mutex);
if (singleton->full_gdscript_cache.has(p_path)) {
return singleton->full_gdscript_cache[p_path];
}
if (singleton->shallow_gdscript_cache.has(p_path)) {
return singleton->shallow_gdscript_cache[p_path];
}
return Ref<GDScript>();
}
Error GDScriptCache::finish_compiling(const String &p_owner) {
MutexLock lock(singleton->mutex);
// Mark this as compiled.
Ref<GDScript> script = get_cached_script(p_owner);
singleton->full_gdscript_cache[p_owner] = script;
singleton->shallow_gdscript_cache.erase(p_owner);
HashSet<String> depends = singleton->dependencies[p_owner];
Error err = OK;
for (const String &E : depends) {
Error this_err = OK;
// No need to save the script. We assume it's already referenced in the owner.
get_full_script(E, this_err);
if (this_err != OK) {
err = this_err;
}
}
singleton->dependencies.erase(p_owner);
return err;
}
void GDScriptCache::add_static_script(Ref<GDScript> p_script) {
ERR_FAIL_COND_MSG(p_script.is_null(), "Trying to cache empty script as static.");
ERR_FAIL_COND_MSG(!p_script->is_valid(), "Trying to cache non-compiled script as static.");
singleton->static_gdscript_cache[p_script->get_fully_qualified_name()] = p_script;
}
void GDScriptCache::remove_static_script(const String &p_fqcn) {
singleton->static_gdscript_cache.erase(p_fqcn);
}
void GDScriptCache::clear() {
if (singleton == nullptr) {
return;
}
MutexLock lock(singleton->mutex);
if (singleton->cleared) {
return;
}
singleton->cleared = true;
singleton->parser_inverse_dependencies.clear();
for (const KeyValue<String, Vector<ObjectID>> &KV : singleton->abandoned_parser_map) {
for (ObjectID parser_ref_id : KV.value) {
Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) };
if (parser_ref.is_valid()) {
parser_ref->clear();
}
}
}
singleton->abandoned_parser_map.clear();
RBSet<Ref<GDScriptParserRef>> parser_map_refs;
for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) {
parser_map_refs.insert(E.value);
}
singleton->parser_map.clear();
for (Ref<GDScriptParserRef> &E : parser_map_refs) {
if (E.is_valid()) {
E->clear();
}
}
parser_map_refs.clear();
singleton->shallow_gdscript_cache.clear();
singleton->full_gdscript_cache.clear();
}
GDScriptCache::GDScriptCache() {
singleton = this;
}
GDScriptCache::~GDScriptCache() {
if (!cleared) {
clear();
}
singleton = nullptr;
}

View file

@ -0,0 +1,121 @@
/**************************************************************************/
/* gdscript_cache.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GDSCRIPT_CACHE_H
#define GDSCRIPT_CACHE_H
#include "gdscript.h"
#include "core/object/ref_counted.h"
#include "core/os/mutex.h"
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
class GDScriptAnalyzer;
class GDScriptParser;
class GDScriptParserRef : public RefCounted {
public:
enum Status {
EMPTY,
PARSED,
INHERITANCE_SOLVED,
INTERFACE_SOLVED,
FULLY_SOLVED,
};
private:
GDScriptParser *parser = nullptr;
GDScriptAnalyzer *analyzer = nullptr;
Status status = EMPTY;
Error result = OK;
String path;
uint32_t source_hash = 0;
bool clearing = false;
bool abandoned = false;
friend class GDScriptCache;
friend class GDScript;
public:
Status get_status() const;
String get_path() const;
uint32_t get_source_hash() const;
GDScriptParser *get_parser();
GDScriptAnalyzer *get_analyzer();
Error raise_status(Status p_new_status);
void clear();
GDScriptParserRef() {}
~GDScriptParserRef();
};
class GDScriptCache {
// String key is full path.
HashMap<String, GDScriptParserRef *> parser_map;
HashMap<String, Vector<ObjectID>> abandoned_parser_map;
HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
HashMap<String, Ref<GDScript>> full_gdscript_cache;
HashMap<String, Ref<GDScript>> static_gdscript_cache;
HashMap<String, HashSet<String>> dependencies;
HashMap<String, HashSet<String>> parser_inverse_dependencies;
friend class GDScript;
friend class GDScriptParserRef;
friend class GDScriptInstance;
static GDScriptCache *singleton;
bool cleared = false;
Mutex mutex;
public:
static void move_script(const String &p_from, const String &p_to);
static void remove_script(const String &p_path);
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
static bool has_parser(const String &p_path);
static void remove_parser(const String &p_path);
static String get_source_code(const String &p_path);
static Vector<uint8_t> get_binary_tokens(const String &p_path);
static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String());
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false);
static Ref<GDScript> get_cached_script(const String &p_path);
static Error finish_compiling(const String &p_owner);
static void add_static_script(Ref<GDScript> p_script);
static void remove_static_script(const String &p_fqcn);
static void clear();
GDScriptCache();
~GDScriptCache();
};
#endif // GDSCRIPT_CACHE_H

View file

@ -0,0 +1,168 @@
/**************************************************************************/
/* gdscript_codegen.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_H
#define GDSCRIPT_CODEGEN_H
#include "gdscript_function.h"
#include "gdscript_utility_functions.h"
#include "core/string/string_name.h"
#include "core/variant/variant.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 clear_temporaries() = 0;
virtual void clear_address(const Address &p_address) = 0;
virtual bool is_local_dirty(const Address &p_address) const = 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, Variant p_rpc_config, 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 GDScriptDataType &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_set_static_variable(const Address &p_value, const Address &p_class, int p_index) = 0;
virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) = 0;
virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_null(const Address &p_target) = 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, bool p_use_conversion) = 0;
virtual void write_store_global(const Address &p_dst, int p_global_index) = 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, const StringName &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_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_native_static_validated(const Address &p_target, MethodBind *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_method_bind_validated(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, bool p_use_self) = 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 write_jump_if_shared(const Address &p_value) = 0;
virtual void write_end_jump_if_shared() = 0;
virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0;
virtual void write_for_assignment(const Address &p_list) = 0;
virtual void write_for(const Address &p_variable, bool p_use_conversion) = 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 write_break() = 0;
virtual void write_continue() = 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_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,190 @@
/**************************************************************************/
/* gdscript_compiler.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_COMPILER_H
#define GDSCRIPT_COMPILER_H
#include "gdscript.h"
#include "gdscript_codegen.h"
#include "gdscript_function.h"
#include "gdscript_parser.h"
#include "core/templates/hash_set.h"
class GDScriptCompiler {
const GDScriptParser *parser = nullptr;
HashSet<GDScript *> parsed_classes;
HashSet<GDScript *> parsing_classes;
GDScript *main_script = nullptr;
struct FunctionLambdaInfo {
GDScriptFunction *function = nullptr;
GDScriptFunction *parent = nullptr;
GDScript *script = nullptr;
StringName name;
int line = 0;
int index = 0;
int depth = 0;
//uint64_t code_hash;
//int code_size;
int capture_count = 0;
bool use_self = false;
int arg_count = 0;
int default_arg_count = 0;
//Vector<GDScriptDataType> argument_types;
//GDScriptDataType return_type;
Vector<FunctionLambdaInfo> sublambdas;
};
struct ScriptLambdaInfo {
Vector<FunctionLambdaInfo> implicit_initializer_info;
Vector<FunctionLambdaInfo> implicit_ready_info;
Vector<FunctionLambdaInfo> static_initializer_info;
HashMap<StringName, Vector<FunctionLambdaInfo>> member_function_infos;
Vector<FunctionLambdaInfo> other_function_infos;
HashMap<StringName, ScriptLambdaInfo> subclass_info;
};
struct CodeGen {
GDScript *script = nullptr;
const GDScriptParser::ClassNode *class_node = nullptr;
const GDScriptParser::FunctionNode *function_node = nullptr;
StringName function_name;
GDScriptCodeGenerator *generator = nullptr;
HashMap<StringName, GDScriptCodeGenerator::Address> parameters;
HashMap<StringName, GDScriptCodeGenerator::Address> locals;
List<HashMap<StringName, GDScriptCodeGenerator::Address>> locals_stack;
bool is_static = false;
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];
}
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];
}
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);
}
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> scr = obj->get_script();
if (scr.is_valid()) {
type.script_type = scr.ptr();
Ref<GDScript> gdscript = scr;
if (gdscript.is_valid()) {
type.kind = GDScriptDataType::GDSCRIPT;
} else {
type.kind = GDScriptDataType::SCRIPT;
}
}
} else {
type.builtin_type = Variant::NIL;
}
}
uint32_t addr = generator->add_or_get_constant(p_constant);
return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CONSTANT, addr, type);
}
void start_block() {
HashMap<StringName, GDScriptCodeGenerator::Address> old_locals = locals;
locals_stack.push_back(old_locals);
generator->start_block();
}
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);
bool _is_class_member_property(GDScript *owner, const StringName &p_name);
bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name);
void _set_error(const String &p_error, const GDScriptParser::Node *p_node);
GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true);
GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false);
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);
List<GDScriptCodeGenerator::Address> _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
void _clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals);
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_clear_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);
GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
Error _prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
FunctionLambdaInfo _get_function_replacement_info(GDScriptFunction *p_func, int p_index = -1, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr);
Vector<FunctionLambdaInfo> _get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr);
ScriptLambdaInfo _get_script_lambda_replacement_info(GDScript *p_script);
bool _do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info);
void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info);
void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos);
void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info);
int err_line = 0;
int err_column = 0;
StringName source;
String error;
GDScriptParser::ExpressionNode *awaited_node = nullptr;
bool has_static_data = false;
public:
static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node);
static void make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false);
String get_error() const;
int get_error_line() const;
int get_error_column() const;
GDScriptCompiler();
};
#endif // GDSCRIPT_COMPILER_H

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,291 @@
/**************************************************************************/
/* gdscript_function.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "gdscript.h"
Variant GDScriptFunction::get_constant(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>");
return constants[p_idx];
}
StringName GDScriptFunction::get_global_name(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, global_names.size(), "<errgname>");
return global_names[p_idx];
}
struct _GDFKC {
int order = 0;
List<int> pos;
};
struct _GDFKCS {
int order = 0;
StringName id;
int pos = 0;
bool operator<(const _GDFKCS &p_r) const {
return order < p_r.order;
}
};
void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const {
int oc = 0;
HashMap<StringName, _GDFKC> sdmap;
for (const StackDebug &sd : stack_debug) {
if (sd.line >= p_line) {
break;
}
if (sd.added) {
if (!sdmap.has(sd.identifier)) {
_GDFKC d;
d.order = oc++;
d.pos.push_back(sd.pos);
sdmap[sd.identifier] = d;
} else {
sdmap[sd.identifier].pos.push_back(sd.pos);
}
} else {
ERR_CONTINUE(!sdmap.has(sd.identifier));
sdmap[sd.identifier].pos.pop_back();
if (sdmap[sd.identifier].pos.is_empty()) {
sdmap.erase(sd.identifier);
}
}
}
List<_GDFKCS> stackpositions;
for (const KeyValue<StringName, _GDFKC> &E : sdmap) {
_GDFKCS spp;
spp.id = E.key;
spp.order = E.value.order;
spp.pos = E.value.pos.back()->get();
stackpositions.push_back(spp);
}
stackpositions.sort();
for (_GDFKCS &E : stackpositions) {
Pair<StringName, int> p;
p.first = E.id;
p.second = E.pos;
r_stackvars->push_back(p);
}
}
GDScriptFunction::GDScriptFunction() {
name = "<anonymous>";
#ifdef DEBUG_ENABLED
{
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->function_list.add(&function_list);
}
#endif
}
GDScriptFunction::~GDScriptFunction() {
get_script()->member_functions.erase(name);
for (int i = 0; i < lambdas.size(); i++) {
memdelete(lambdas[i]);
}
for (int i = 0; i < argument_types.size(); i++) {
argument_types.write[i].script_type_ref = Ref<Script>();
}
return_type.script_type_ref = Ref<Script>();
#ifdef DEBUG_ENABLED
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
GDScriptLanguage::get_singleton()->function_list.remove(&function_list);
#endif
}
/////////////////////
Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Variant arg;
r_error.error = Callable::CallError::CALL_OK;
if (p_argcount == 0) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.expected = 1;
return Variant();
} else if (p_argcount == 1) {
//noooneee
} else if (p_argcount == 2) {
arg = *p_args[0];
} else {
Array extra_args;
for (int i = 0; i < p_argcount - 1; i++) {
extra_args.push_back(*p_args[i]);
}
arg = extra_args;
}
Ref<GDScriptFunctionState> self = *p_args[p_argcount - 1];
if (self.is_null()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = p_argcount - 1;
r_error.expected = Variant::OBJECT;
return Variant();
}
return resume(arg);
}
bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
if (function == nullptr) {
return false;
}
if (p_extended_check) {
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
// Script gone?
if (!scripts_list.in_list()) {
return false;
}
// Class instance gone? (if not static function)
if (state.instance && !instances_list.in_list()) {
return false;
}
}
return true;
}
Variant GDScriptFunctionState::resume(const Variant &p_arg) {
ERR_FAIL_NULL_V(function, Variant());
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
if (!scripts_list.in_list()) {
#ifdef DEBUG_ENABLED
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but script is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
return Variant();
#endif
}
if (state.instance && !instances_list.in_list()) {
#ifdef DEBUG_ENABLED
ERR_FAIL_V_MSG(Variant(), "Resumed function '" + state.function_name + "()' after await, but class instance is gone. At script: " + state.script_path + ":" + itos(state.line));
#else
return Variant();
#endif
}
// Do these now to avoid locking again after the call
scripts_list.remove_from_list();
instances_list.remove_from_list();
}
state.result = p_arg;
Callable::CallError err;
Variant ret = function->call(nullptr, nullptr, 0, err, &state);
bool completed = true;
// If the return value is a GDScriptFunctionState reference,
// then the function did await again after resuming.
if (ret.is_ref_counted()) {
GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
if (gdfs && gdfs->function == function) {
completed = false;
gdfs->first_state = first_state.is_valid() ? first_state : Ref<GDScriptFunctionState>(this);
}
}
function = nullptr; //cleaned up;
state.result = Variant();
if (completed) {
if (first_state.is_valid()) {
first_state->emit_signal(SNAME("completed"), ret);
} else {
emit_signal(SNAME("completed"), ret);
}
#ifdef DEBUG_ENABLED
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->exit_function();
}
_clear_stack();
#endif
}
return ret;
}
void GDScriptFunctionState::_clear_stack() {
if (state.stack_size) {
Variant *stack = (Variant *)state.stack.ptr();
// The first 3 are special addresses and not copied to the state, so we skip them here.
for (int i = 3; i < state.stack_size; i++) {
stack[i].~Variant();
}
state.stack_size = 0;
}
}
void GDScriptFunctionState::_clear_connections() {
List<Object::Connection> conns;
get_signals_connected_to_this(&conns);
for (Object::Connection &c : conns) {
c.signal.disconnect(c.callable);
}
}
void GDScriptFunctionState::_bind_methods() {
ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false));
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &GDScriptFunctionState::_signal_callback, MethodInfo("_signal_callback"));
ADD_SIGNAL(MethodInfo("completed", PropertyInfo(Variant::NIL, "result", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
}
GDScriptFunctionState::GDScriptFunctionState() :
scripts_list(this),
instances_list(this) {
}
GDScriptFunctionState::~GDScriptFunctionState() {
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
scripts_list.remove_from_list();
instances_list.remove_from_list();
}
}

View file

@ -0,0 +1,583 @@
/**************************************************************************/
/* gdscript_function.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_FUNCTION_H
#define GDSCRIPT_FUNCTION_H
#include "gdscript_utility_functions.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/os/thread.h"
#include "core/string/string_name.h"
#include "core/templates/pair.h"
#include "core/templates/self_list.h"
#include "core/variant/variant.h"
class GDScriptInstance;
class GDScript;
class GDScriptDataType {
public:
Vector<GDScriptDataType> container_element_types;
enum Kind {
UNINITIALIZED,
BUILTIN,
NATIVE,
SCRIPT,
GDSCRIPT,
};
Kind kind = UNINITIALIZED;
bool has_type = false;
Variant::Type builtin_type = Variant::NIL;
StringName native_type;
Script *script_type = nullptr;
Ref<Script> script_type_ref;
bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const {
if (!has_type) {
return true; // Can't type check
}
switch (kind) {
case UNINITIALIZED:
break;
case BUILTIN: {
Variant::Type var_type = p_variant.get_type();
bool valid = builtin_type == var_type;
if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
Array array = p_variant;
if (array.is_typed()) {
const GDScriptDataType &elem_type = container_element_types[0];
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 = (elem_type.kind == SCRIPT || elem_type.kind == GDSCRIPT) && elem_type.script_type == array_script_type_ref.ptr();
} else if (array_native_type != StringName()) {
valid = elem_type.kind == NATIVE && elem_type.native_type == array_native_type;
} else {
valid = elem_type.kind == BUILTIN && elem_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;
} break;
case NATIVE: {
if (p_variant.get_type() == Variant::NIL) {
return true;
}
if (p_variant.get_type() != Variant::OBJECT) {
return false;
}
bool was_freed = false;
Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
return !was_freed;
}
if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) {
return false;
}
return true;
} break;
case SCRIPT:
case GDSCRIPT: {
if (p_variant.get_type() == Variant::NIL) {
return true;
}
if (p_variant.get_type() != Variant::OBJECT) {
return false;
}
bool was_freed = false;
Object *obj = p_variant.get_validated_object_with_check(was_freed);
if (!obj) {
return !was_freed;
}
Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : nullptr;
bool valid = false;
while (base.is_valid()) {
if (base == script_type) {
valid = true;
break;
}
base = base->get_base_script();
}
return valid;
} break;
}
return false;
}
bool can_contain_object() const {
if (has_type && kind == BUILTIN) {
switch (builtin_type) {
case Variant::ARRAY:
if (has_container_element_type(0)) {
return container_element_types[0].can_contain_object();
}
return true;
case Variant::DICTIONARY:
case Variant::NIL:
case Variant::OBJECT:
return true;
default:
return false;
}
}
return true;
}
void set_container_element_type(int p_index, const GDScriptDataType &p_element_type) {
ERR_FAIL_COND(p_index < 0);
while (p_index >= container_element_types.size()) {
container_element_types.push_back(GDScriptDataType());
}
container_element_types.write[p_index] = GDScriptDataType(p_element_type);
}
GDScriptDataType get_container_element_type(int p_index) const {
ERR_FAIL_INDEX_V(p_index, container_element_types.size(), GDScriptDataType());
return container_element_types[p_index];
}
GDScriptDataType get_container_element_type_or_variant(int p_index) const {
if (p_index < 0 || p_index >= container_element_types.size()) {
return GDScriptDataType();
}
return container_element_types[p_index];
}
bool has_container_element_type(int p_index) const {
return p_index >= 0 && p_index < container_element_types.size();
}
bool has_container_element_types() const {
return !container_element_types.is_empty();
}
GDScriptDataType() = default;
void 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;
container_element_types = p_other.container_element_types;
}
GDScriptDataType(const GDScriptDataType &p_other) {
*this = p_other;
}
~GDScriptDataType() {}
};
class GDScriptFunction {
public:
enum Opcode {
OPCODE_OPERATOR,
OPCODE_OPERATOR_VALIDATED,
OPCODE_TYPE_TEST_BUILTIN,
OPCODE_TYPE_TEST_ARRAY,
OPCODE_TYPE_TEST_NATIVE,
OPCODE_TYPE_TEST_SCRIPT,
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_SET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_ASSIGN,
OPCODE_ASSIGN_NULL,
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_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_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_BUILTIN_STATIC,
OPCODE_CALL_NATIVE_STATIC,
OPCODE_CALL_NATIVE_STATIC_VALIDATED_RETURN,
OPCODE_CALL_NATIVE_STATIC_VALIDATED_NO_RETURN,
OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN,
OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN,
OPCODE_AWAIT,
OPCODE_AWAIT_RESUME,
OPCODE_CREATE_LAMBDA,
OPCODE_CREATE_SELF_LAMBDA,
OPCODE_JUMP,
OPCODE_JUMP_IF,
OPCODE_JUMP_IF_NOT,
OPCODE_JUMP_TO_DEF_ARGUMENT,
OPCODE_JUMP_IF_SHARED,
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_PACKED_VECTOR4_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_PACKED_VECTOR4_ARRAY,
OPCODE_ITERATE_OBJECT,
OPCODE_STORE_GLOBAL,
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_VECTOR4,
OPCODE_TYPE_ADJUST_VECTOR4I,
OPCODE_TYPE_ADJUST_PLANE,
OPCODE_TYPE_ADJUST_QUATERNION,
OPCODE_TYPE_ADJUST_AABB,
OPCODE_TYPE_ADJUST_BASIS,
OPCODE_TYPE_ADJUST_TRANSFORM3D,
OPCODE_TYPE_ADJUST_PROJECTION,
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_TYPE_ADJUST_PACKED_VECTOR4_ARRAY,
OPCODE_ASSERT,
OPCODE_BREAKPOINT,
OPCODE_LINE,
OPCODE_END
};
enum Address {
ADDR_BITS = 24,
ADDR_MASK = ((1 << ADDR_BITS) - 1),
ADDR_TYPE_MASK = ~ADDR_MASK,
ADDR_TYPE_STACK = 0,
ADDR_TYPE_CONSTANT = 1,
ADDR_TYPE_MEMBER = 2,
ADDR_TYPE_MAX = 3,
};
enum FixedAddresses {
ADDR_STACK_SELF = 0,
ADDR_STACK_CLASS = 1,
ADDR_STACK_NIL = 2,
FIXED_ADDRESSES_MAX = 3,
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),
};
struct StackDebug {
int line;
int pos;
bool added;
StringName identifier;
};
private:
friend class GDScript;
friend class GDScriptCompiler;
friend class GDScriptByteCodeGenerator;
friend class GDScriptLanguage;
StringName name;
StringName source;
bool _static = false;
Vector<GDScriptDataType> argument_types;
GDScriptDataType return_type;
MethodInfo method_info;
Variant rpc_config;
GDScript *_script = nullptr;
int _initial_line = 0;
int _argument_count = 0;
int _stack_size = 0;
int _instruction_args_size = 0;
SelfList<GDScriptFunction> function_list{ this };
mutable Variant nil;
HashMap<int, Variant::Type> temporary_slots;
List<StackDebug> stack_debug;
Vector<int> code;
Vector<int> default_arguments;
Vector<Variant> constants;
Vector<StringName> global_names;
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;
int _code_size = 0;
int _default_arg_count = 0;
int _constant_count = 0;
int _global_names_count = 0;
int _operator_funcs_count = 0;
int _setters_count = 0;
int _getters_count = 0;
int _keyed_setters_count = 0;
int _keyed_getters_count = 0;
int _indexed_setters_count = 0;
int _indexed_getters_count = 0;
int _builtin_methods_count = 0;
int _constructors_count = 0;
int _utilities_count = 0;
int _gds_utilities_count = 0;
int _methods_count = 0;
int _lambdas_count = 0;
int *_code_ptr = nullptr;
const int *_default_arg_ptr = nullptr;
mutable Variant *_constants_ptr = nullptr;
const StringName *_global_names_ptr = nullptr;
const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr;
const Variant::ValidatedSetter *_setters_ptr = nullptr;
const Variant::ValidatedGetter *_getters_ptr = nullptr;
const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr;
const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr;
const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr;
const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr;
const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr;
const Variant::ValidatedConstructor *_constructors_ptr = nullptr;
const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr;
const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr;
MethodBind **_methods_ptr = nullptr;
GDScriptFunction **_lambdas_ptr = nullptr;
#ifdef DEBUG_ENABLED
CharString func_cname;
const char *_func_cname = nullptr;
Vector<String> operator_names;
Vector<String> setter_names;
Vector<String> getter_names;
Vector<String> builtin_methods_names;
Vector<String> constructors_names;
Vector<String> utilities_names;
Vector<String> gds_utilities_names;
struct Profile {
StringName signature;
SafeNumeric<uint64_t> call_count;
SafeNumeric<uint64_t> self_time;
SafeNumeric<uint64_t> total_time;
SafeNumeric<uint64_t> frame_call_count;
SafeNumeric<uint64_t> frame_self_time;
SafeNumeric<uint64_t> frame_total_time;
uint64_t last_frame_call_count = 0;
uint64_t last_frame_self_time = 0;
uint64_t last_frame_total_time = 0;
typedef struct NativeProfile {
uint64_t call_count;
uint64_t total_time;
String signature;
} NativeProfile;
HashMap<String, NativeProfile> native_calls;
HashMap<String, NativeProfile> last_native_calls;
} profile;
#endif
_FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const;
Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type);
public:
static constexpr int MAX_CALL_DEPTH = 2048; // Limit to try to avoid crash because of a stack overflow.
struct CallState {
GDScript *script = nullptr;
GDScriptInstance *instance = nullptr;
#ifdef DEBUG_ENABLED
StringName function_name;
String script_path;
#endif
Vector<uint8_t> stack;
int stack_size = 0;
uint32_t alloca_size = 0;
int ip = 0;
int line = 0;
int defarg = 0;
Variant result;
};
_FORCE_INLINE_ StringName get_name() const { return name; }
_FORCE_INLINE_ StringName get_source() const { return source; }
_FORCE_INLINE_ GDScript *get_script() const { return _script; }
_FORCE_INLINE_ bool is_static() const { return _static; }
_FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; }
_FORCE_INLINE_ int get_argument_count() const { return _argument_count; }
_FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; }
_FORCE_INLINE_ int get_max_stack_size() const { return _stack_size; }
Variant get_constant(int p_idx) const;
StringName get_global_name(int p_idx) const;
Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr);
void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const;
#ifdef DEBUG_ENABLED
void _profile_native_call(uint64_t p_t_taken, const String &p_function_name, const String &p_instance_class_name = String());
void disassemble(const Vector<String> &p_code_lines) const;
#endif
GDScriptFunction();
~GDScriptFunction();
};
class GDScriptFunctionState : public RefCounted {
GDCLASS(GDScriptFunctionState, RefCounted);
friend class GDScriptFunction;
GDScriptFunction *function = nullptr;
GDScriptFunction::CallState state;
Variant _signal_callback(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Ref<GDScriptFunctionState> first_state;
SelfList<GDScriptFunctionState> scripts_list;
SelfList<GDScriptFunctionState> instances_list;
protected:
static void _bind_methods();
public:
bool is_valid(bool p_extended_check = false) const;
Variant resume(const Variant &p_arg = Variant());
void _clear_stack();
void _clear_connections();
GDScriptFunctionState();
~GDScriptFunctionState();
};
#endif // GDSCRIPT_FUNCTION_H

View file

@ -0,0 +1,302 @@
/**************************************************************************/
/* gdscript_lambda_callable.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "gdscript.h"
#include "core/templates/hashfuncs.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;
}
bool GDScriptLambdaCallable::is_valid() const {
return CallableCustom::is_valid() && function != nullptr;
}
uint32_t GDScriptLambdaCallable::hash() const {
return h;
}
String GDScriptLambdaCallable::get_as_text() const {
if (function == nullptr) {
return "<invalid lambda>";
}
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();
}
StringName GDScriptLambdaCallable::get_method() const {
return function->get_name();
}
int GDScriptLambdaCallable::get_argument_count(bool &r_is_valid) const {
if (function == nullptr) {
r_is_valid = false;
return 0;
}
r_is_valid = true;
return function->get_argument_count() - captures.size();
}
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 (function == nullptr) {
r_return_value = Variant();
r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return;
}
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];
if (captures[i].get_type() == Variant::OBJECT) {
bool was_freed = false;
captures[i].get_validated_object_with_check(was_freed);
if (was_freed) {
ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
static Variant nil;
args.write[i] = &nil;
}
}
}
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);
switch (r_call_error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
r_call_error.argument -= captures_amount;
#ifdef DEBUG_ENABLED
if (r_call_error.argument < 0) {
ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument));
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
r_call_error.argument = 0;
r_call_error.expected = 0;
}
#endif
break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
r_call_error.expected -= captures_amount;
#ifdef DEBUG_ENABLED
if (r_call_error.expected < 0) {
ERR_PRINT("GDScript bug (please report): Invalid lambda captures count.");
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
r_call_error.argument = 0;
r_call_error.expected = 0;
}
#endif
break;
default:
break;
}
} 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) :
function(p_function) {
ERR_FAIL_NULL(p_script.ptr());
ERR_FAIL_NULL(p_function);
script = p_script;
captures = p_captures;
h = (uint32_t)hash_murmur3_one_64((uint64_t)this);
}
bool GDScriptLambdaSelfCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
// Lambda callables are only compared by reference.
return p_a == p_b;
}
bool GDScriptLambdaSelfCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
// Lambda callables are only compared by reference.
return p_a < p_b;
}
bool GDScriptLambdaSelfCallable::is_valid() const {
return CallableCustom::is_valid() && function != nullptr;
}
uint32_t GDScriptLambdaSelfCallable::hash() const {
return h;
}
String GDScriptLambdaSelfCallable::get_as_text() const {
if (function == nullptr) {
return "<invalid lambda>";
}
if (function->get_name() != StringName()) {
return function->get_name().operator String() + "(lambda)";
}
return "(anonymous lambda)";
}
CallableCustom::CompareEqualFunc GDScriptLambdaSelfCallable::get_compare_equal_func() const {
return compare_equal;
}
CallableCustom::CompareLessFunc GDScriptLambdaSelfCallable::get_compare_less_func() const {
return compare_less;
}
ObjectID GDScriptLambdaSelfCallable::get_object() const {
return object->get_instance_id();
}
StringName GDScriptLambdaSelfCallable::get_method() const {
return function->get_name();
}
int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const {
if (function == nullptr) {
r_is_valid = false;
return 0;
}
r_is_valid = true;
return function->get_argument_count() - captures.size();
}
void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
#ifdef DEBUG_ENABLED
if (object->get_script_instance() == nullptr || object->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
ERR_PRINT("Trying to call a lambda with an invalid instance.");
r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return;
}
#endif
int captures_amount = captures.size();
if (function == nullptr) {
r_return_value = Variant();
r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return;
}
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];
if (captures[i].get_type() == Variant::OBJECT) {
bool was_freed = false;
captures[i].get_validated_object_with_check(was_freed);
if (was_freed) {
ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i));
static Variant nil;
args.write[i] = &nil;
}
}
}
for (int i = 0; i < p_argcount; i++) {
args.write[i + captures_amount] = p_arguments[i];
}
r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error);
switch (r_call_error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
r_call_error.argument -= captures_amount;
#ifdef DEBUG_ENABLED
if (r_call_error.argument < 0) {
ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument));
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
r_call_error.argument = 0;
r_call_error.expected = 0;
}
#endif
break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
r_call_error.expected -= captures_amount;
#ifdef DEBUG_ENABLED
if (r_call_error.expected < 0) {
ERR_PRINT("GDScript bug (please report): Invalid lambda captures count.");
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code.
r_call_error.argument = 0;
r_call_error.expected = 0;
}
#endif
break;
default:
break;
}
} else {
r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), p_arguments, p_argcount, r_call_error);
}
}
GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
ERR_FAIL_NULL(p_self.ptr());
ERR_FAIL_NULL(p_function);
reference = p_self;
object = p_self.ptr();
captures = p_captures;
h = (uint32_t)hash_murmur3_one_64((uint64_t)this);
}
GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
ERR_FAIL_NULL(p_self);
ERR_FAIL_NULL(p_function);
object = p_self;
captures = p_captures;
h = (uint32_t)hash_murmur3_one_64((uint64_t)this);
}

View file

@ -0,0 +1,101 @@
/**************************************************************************/
/* gdscript_lambda_callable.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_LAMBDA_CALLABLE_H
#define GDSCRIPT_LAMBDA_CALLABLE_H
#include "gdscript.h"
#include "core/object/ref_counted.h"
#include "core/templates/vector.h"
#include "core/variant/callable.h"
#include "core/variant/variant.h"
class GDScriptFunction;
class GDScriptInstance;
class GDScriptLambdaCallable : public CallableCustom {
GDScript::UpdatableFuncPtr function;
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);
public:
bool is_valid() const override;
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;
StringName get_method() const override;
int get_argument_count(bool &r_is_valid) const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
GDScriptLambdaCallable(GDScriptLambdaCallable &) = delete;
GDScriptLambdaCallable(const GDScriptLambdaCallable &) = delete;
GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
virtual ~GDScriptLambdaCallable() = default;
};
// Lambda callable that references a particular object, so it can use `self` in the body.
class GDScriptLambdaSelfCallable : public CallableCustom {
GDScript::UpdatableFuncPtr function;
Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference.
Object *object = nullptr; // For non RefCounted objects, use a direct pointer.
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);
public:
bool is_valid() const override;
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;
StringName get_method() const override;
int get_argument_count(bool &r_is_valid) const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
GDScriptLambdaSelfCallable(GDScriptLambdaSelfCallable &) = delete;
GDScriptLambdaSelfCallable(const GDScriptLambdaSelfCallable &) = delete;
GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures);
virtual ~GDScriptLambdaSelfCallable() = default;
};
#endif // GDSCRIPT_LAMBDA_CALLABLE_H

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,96 @@
/**************************************************************************/
/* gdscript_rpc_callable.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_rpc_callable.h"
#include "core/object/script_language.h"
#include "core/templates/hashfuncs.h"
#include "scene/main/node.h"
bool GDScriptRPCCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
return p_a->hash() == p_b->hash();
}
bool GDScriptRPCCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
return p_a->hash() < p_b->hash();
}
uint32_t GDScriptRPCCallable::hash() const {
return h;
}
String GDScriptRPCCallable::get_as_text() const {
String class_name = object->get_class();
Ref<Script> script = object->get_script();
return class_name + "(" + script->get_path().get_file() + ")::" + String(method) + " (rpc)";
}
CallableCustom::CompareEqualFunc GDScriptRPCCallable::get_compare_equal_func() const {
return compare_equal;
}
CallableCustom::CompareLessFunc GDScriptRPCCallable::get_compare_less_func() const {
return compare_less;
}
ObjectID GDScriptRPCCallable::get_object() const {
return object->get_instance_id();
}
StringName GDScriptRPCCallable::get_method() const {
return method;
}
int GDScriptRPCCallable::get_argument_count(bool &r_is_valid) const {
return object->get_method_argument_count(method, &r_is_valid);
}
void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
r_return_value = object->callp(method, p_arguments, p_argcount, r_call_error);
}
GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_method) {
ERR_FAIL_NULL(p_object);
object = p_object;
method = p_method;
h = method.hash();
h = hash_murmur3_one_64(object->get_instance_id(), h);
node = Object::cast_to<Node>(object);
ERR_FAIL_NULL_MSG(node, "RPC can only be defined on class that extends Node.");
}
Error GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const {
if (unlikely(!node)) {
r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return ERR_UNCONFIGURED;
}
r_call_error.error = Callable::CallError::CALL_OK;
return node->rpcp(p_peer_id, method, p_arguments, p_argcount);
}

View file

@ -0,0 +1,63 @@
/**************************************************************************/
/* gdscript_rpc_callable.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_RPC_CALLABLE_H
#define GDSCRIPT_RPC_CALLABLE_H
#include "core/variant/callable.h"
#include "core/variant/variant.h"
class Node;
class GDScriptRPCCallable : public CallableCustom {
Object *object = nullptr;
Node *node = nullptr;
StringName method;
uint32_t h = 0;
static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
public:
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;
StringName get_method() const override;
int get_argument_count(bool &r_is_valid) const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override;
GDScriptRPCCallable(Object *p_object, const StringName &p_method);
virtual ~GDScriptRPCCallable() = default;
};
#endif // GDSCRIPT_RPC_CALLABLE_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,316 @@
/**************************************************************************/
/* gdscript_tokenizer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_TOKENIZER_H
#define GDSCRIPT_TOKENIZER_H
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
#include "core/templates/list.h"
#include "core/templates/vector.h"
#include "core/variant/variant.h"
#ifdef MINGW_ENABLED
#undef CONST
#undef IN
#undef VOID
#endif
class GDScriptTokenizer {
public:
enum CursorPlace {
CURSOR_NONE,
CURSOR_BEGINNING,
CURSOR_MIDDLE,
CURSOR_END,
};
struct Token {
enum Type {
EMPTY,
// Basic
ANNOTATION,
IDENTIFIER,
LITERAL,
// Comparison
LESS,
LESS_EQUAL,
GREATER,
GREATER_EQUAL,
EQUAL_EQUAL,
BANG_EQUAL,
// Logical
AND,
OR,
NOT,
AMPERSAND_AMPERSAND,
PIPE_PIPE,
BANG,
// Bitwise
AMPERSAND,
PIPE,
TILDE,
CARET,
LESS_LESS,
GREATER_GREATER,
// Math
PLUS,
MINUS,
STAR,
STAR_STAR,
SLASH,
PERCENT,
// Assignment
EQUAL,
PLUS_EQUAL,
MINUS_EQUAL,
STAR_EQUAL,
STAR_STAR_EQUAL,
SLASH_EQUAL,
PERCENT_EQUAL,
LESS_LESS_EQUAL,
GREATER_GREATER_EQUAL,
AMPERSAND_EQUAL,
PIPE_EQUAL,
CARET_EQUAL,
// Control flow
IF,
ELIF,
ELSE,
FOR,
WHILE,
BREAK,
CONTINUE,
PASS,
RETURN,
MATCH,
WHEN,
// Keywords
AS,
ASSERT,
AWAIT,
BREAKPOINT,
CLASS,
CLASS_NAME,
CONST,
ENUM,
EXTENDS,
FUNC,
IN,
IS,
NAMESPACE,
PRELOAD,
SELF,
SIGNAL,
STATIC,
SUPER,
TRAIT,
VAR,
VOID,
YIELD,
// Punctuation
BRACKET_OPEN,
BRACKET_CLOSE,
BRACE_OPEN,
BRACE_CLOSE,
PARENTHESIS_OPEN,
PARENTHESIS_CLOSE,
COMMA,
SEMICOLON,
PERIOD,
PERIOD_PERIOD,
COLON,
DOLLAR,
FORWARD_ARROW,
UNDERSCORE,
// Whitespace
NEWLINE,
INDENT,
DEDENT,
// Constants
CONST_PI,
CONST_TAU,
CONST_INF,
CONST_NAN,
// Error message improvement
VCS_CONFLICT_MARKER,
BACKTICK,
QUESTION_MARK,
// Special
ERROR,
TK_EOF, // "EOF" is reserved
TK_MAX
};
Type type = EMPTY;
Variant literal;
int start_line = 0, end_line = 0, start_column = 0, end_column = 0;
int leftmost_column = 0, rightmost_column = 0; // Column span for multiline tokens.
int cursor_position = -1;
CursorPlace cursor_place = CURSOR_NONE;
String source;
const char *get_name() const;
bool can_precede_bin_op() const;
bool is_identifier() const;
bool is_node_name() const;
StringName get_identifier() const { return literal; }
Token(Type p_type) {
type = p_type;
}
Token() {}
};
#ifdef TOOLS_ENABLED
struct CommentData {
String comment;
// true: Comment starts at beginning of line or after indentation.
// false: Inline comment (starts after some code).
bool new_line = false;
CommentData() {}
CommentData(const String &p_comment, bool p_new_line) {
comment = p_comment;
new_line = p_new_line;
}
};
virtual const HashMap<int, CommentData> &get_comments() const = 0;
#endif // TOOLS_ENABLED
static String get_token_name(Token::Type p_token_type);
virtual int get_cursor_line() const = 0;
virtual int get_cursor_column() const = 0;
virtual void set_cursor_position(int p_line, int p_column) = 0;
virtual void set_multiline_mode(bool p_state) = 0;
virtual bool is_past_cursor() const = 0;
virtual void push_expression_indented_block() = 0; // For lambdas, or blocks inside expressions.
virtual void pop_expression_indented_block() = 0; // For lambdas, or blocks inside expressions.
virtual bool is_text() = 0;
virtual Token scan() = 0;
virtual ~GDScriptTokenizer() {}
};
class GDScriptTokenizerText : public GDScriptTokenizer {
String source;
const char32_t *_source = nullptr;
const char32_t *_current = nullptr;
int line = -1, column = -1;
int cursor_line = -1, cursor_column = -1;
int tab_size = 4;
// Keep track of multichar tokens.
const char32_t *_start = nullptr;
int start_line = 0, start_column = 0;
int leftmost_column = 0, rightmost_column = 0;
// Info cache.
bool line_continuation = false; // Whether this line is a continuation of the previous, like when using '\'.
bool multiline_mode = false;
List<Token> error_stack;
bool pending_newline = false;
Token last_token;
Token last_newline;
int pending_indents = 0;
List<int> indent_stack;
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;
Vector<int> continuation_lines;
#ifdef DEBUG_ENABLED
Vector<String> keyword_list;
#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
HashMap<int, CommentData> comments;
#endif // TOOLS_ENABLED
_FORCE_INLINE_ bool _is_at_end() { return position >= length; }
_FORCE_INLINE_ char32_t _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; }
int indent_level() const { return indent_stack.size(); }
bool has_error() const { return !error_stack.is_empty(); }
Token pop_error();
char32_t _advance();
String _get_indent_char_name(char32_t ch);
void _skip_whitespace();
void check_indent();
#ifdef DEBUG_ENABLED
void make_keyword_list();
#endif // DEBUG_ENABLED
Token make_error(const String &p_message);
void push_error(const String &p_message);
void push_error(const Token &p_error);
Token make_paren_error(char32_t p_paren);
Token make_token(Token::Type p_type);
Token make_literal(const Variant &p_literal);
Token make_identifier(const StringName &p_identifier);
Token check_vcs_marker(char32_t p_test, Token::Type p_double_type);
void push_paren(char32_t p_char);
bool pop_paren(char32_t p_expected);
void newline(bool p_make_token);
Token number();
Token potential_identifier();
Token string();
Token annotation();
public:
void set_source_code(const String &p_source_code);
const Vector<int> &get_continuation_lines() const { return continuation_lines; }
virtual int get_cursor_line() const override;
virtual int get_cursor_column() const override;
virtual void set_cursor_position(int p_line, int p_column) override;
virtual void set_multiline_mode(bool p_state) override;
virtual bool is_past_cursor() const override;
virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual bool is_text() override { return true; }
#ifdef TOOLS_ENABLED
virtual const HashMap<int, CommentData> &get_comments() const override {
return comments;
}
#endif // TOOLS_ENABLED
virtual Token scan() override;
GDScriptTokenizerText();
};
#endif // GDSCRIPT_TOKENIZER_H

View file

@ -0,0 +1,493 @@
/**************************************************************************/
/* gdscript_tokenizer_buffer.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_tokenizer_buffer.h"
#include "core/io/compression.h"
#include "core/io/marshalls.h"
#define TOKENIZER_VERSION 100
int GDScriptTokenizerBuffer::_token_to_binary(const Token &p_token, Vector<uint8_t> &r_buffer, int p_start, HashMap<StringName, uint32_t> &r_identifiers_map, HashMap<Variant, uint32_t, VariantHasher, VariantComparator> &r_constants_map) {
int pos = p_start;
int token_type = p_token.type & TOKEN_MASK;
switch (p_token.type) {
case GDScriptTokenizer::Token::ANNOTATION:
case GDScriptTokenizer::Token::IDENTIFIER: {
// Add identifier to map.
int identifier_pos;
StringName id = p_token.get_identifier();
if (r_identifiers_map.has(id)) {
identifier_pos = r_identifiers_map[id];
} else {
identifier_pos = r_identifiers_map.size();
r_identifiers_map[id] = identifier_pos;
}
token_type |= identifier_pos << TOKEN_BITS;
} break;
case GDScriptTokenizer::Token::ERROR:
case GDScriptTokenizer::Token::LITERAL: {
// Add literal to map.
int constant_pos;
if (r_constants_map.has(p_token.literal)) {
constant_pos = r_constants_map[p_token.literal];
} else {
constant_pos = r_constants_map.size();
r_constants_map[p_token.literal] = constant_pos;
}
token_type |= constant_pos << TOKEN_BITS;
} break;
default:
break;
}
// Encode token.
int token_len;
if (token_type & TOKEN_MASK) {
token_len = 8;
r_buffer.resize(pos + token_len);
encode_uint32(token_type | TOKEN_BYTE_MASK, &r_buffer.write[pos]);
pos += 4;
} else {
token_len = 5;
r_buffer.resize(pos + token_len);
r_buffer.write[pos] = token_type;
pos++;
}
encode_uint32(p_token.start_line, &r_buffer.write[pos]);
return token_len;
}
GDScriptTokenizer::Token GDScriptTokenizerBuffer::_binary_to_token(const uint8_t *p_buffer) {
Token token;
const uint8_t *b = p_buffer;
uint32_t token_type = decode_uint32(b);
token.type = (Token::Type)(token_type & TOKEN_MASK);
if (token_type & TOKEN_BYTE_MASK) {
b += 4;
} else {
b++;
}
token.start_line = decode_uint32(b);
token.end_line = token.start_line;
token.literal = token.get_name();
if (token.type == Token::CONST_NAN) {
token.literal = String("NAN"); // Special case since name and notation are different.
}
switch (token.type) {
case GDScriptTokenizer::Token::ANNOTATION:
case GDScriptTokenizer::Token::IDENTIFIER: {
// Get name from map.
int identifier_pos = token_type >> TOKEN_BITS;
if (unlikely(identifier_pos >= identifiers.size())) {
Token error;
error.type = Token::ERROR;
error.literal = "Identifier index out of bounds.";
return error;
}
token.literal = identifiers[identifier_pos];
} break;
case GDScriptTokenizer::Token::ERROR:
case GDScriptTokenizer::Token::LITERAL: {
// Get literal from map.
int constant_pos = token_type >> TOKEN_BITS;
if (unlikely(constant_pos >= constants.size())) {
Token error;
error.type = Token::ERROR;
error.literal = "Constant index out of bounds.";
return error;
}
token.literal = constants[constant_pos];
} break;
default:
break;
}
return token;
}
Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) {
const uint8_t *buf = p_buffer.ptr();
ERR_FAIL_COND_V(p_buffer.size() < 12 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA);
int version = decode_uint32(&buf[4]);
ERR_FAIL_COND_V_MSG(version > TOKENIZER_VERSION, ERR_INVALID_DATA, "Binary GDScript is too recent! Please use a newer engine version.");
int decompressed_size = decode_uint32(&buf[8]);
Vector<uint8_t> contents;
if (decompressed_size == 0) {
contents = p_buffer.slice(12);
} else {
contents.resize(decompressed_size);
int result = Compression::decompress(contents.ptrw(), contents.size(), &buf[12], p_buffer.size() - 12, Compression::MODE_ZSTD);
ERR_FAIL_COND_V_MSG(result != decompressed_size, ERR_INVALID_DATA, "Error decompressing GDScript tokenizer buffer.");
}
int total_len = contents.size();
buf = contents.ptr();
uint32_t identifier_count = decode_uint32(&buf[0]);
uint32_t constant_count = decode_uint32(&buf[4]);
uint32_t token_line_count = decode_uint32(&buf[8]);
uint32_t token_count = decode_uint32(&buf[16]);
const uint8_t *b = &buf[20];
total_len -= 20;
identifiers.resize(identifier_count);
for (uint32_t i = 0; i < identifier_count; i++) {
uint32_t len = decode_uint32(b);
total_len -= 4;
ERR_FAIL_COND_V((len * 4u) > (uint32_t)total_len, ERR_INVALID_DATA);
b += 4;
Vector<uint32_t> cs;
cs.resize(len);
for (uint32_t j = 0; j < len; j++) {
uint8_t tmp[4];
for (uint32_t k = 0; k < 4; k++) {
tmp[k] = b[j * 4 + k] ^ 0xb6;
}
cs.write[j] = decode_uint32(tmp);
}
String s(reinterpret_cast<const char32_t *>(cs.ptr()), len);
b += len * 4;
total_len -= len * 4;
identifiers.write[i] = s;
}
constants.resize(constant_count);
for (uint32_t i = 0; i < constant_count; i++) {
Variant v;
int len;
Error err = decode_variant(v, b, total_len, &len, false);
if (err) {
return err;
}
b += len;
total_len -= len;
constants.write[i] = v;
}
for (uint32_t i = 0; i < token_line_count; i++) {
ERR_FAIL_COND_V(total_len < 8, ERR_INVALID_DATA);
uint32_t token_index = decode_uint32(b);
b += 4;
uint32_t line = decode_uint32(b);
b += 4;
total_len -= 8;
token_lines[token_index] = line;
}
for (uint32_t i = 0; i < token_line_count; i++) {
ERR_FAIL_COND_V(total_len < 8, ERR_INVALID_DATA);
uint32_t token_index = decode_uint32(b);
b += 4;
uint32_t column = decode_uint32(b);
b += 4;
total_len -= 8;
token_columns[token_index] = column;
}
tokens.resize(token_count);
for (uint32_t i = 0; i < token_count; i++) {
int token_len = 5;
if ((*b) & TOKEN_BYTE_MASK) {
token_len = 8;
}
ERR_FAIL_COND_V(total_len < token_len, ERR_INVALID_DATA);
Token token = _binary_to_token(b);
b += token_len;
ERR_FAIL_INDEX_V(token.type, Token::TK_MAX, ERR_INVALID_DATA);
tokens.write[i] = token;
total_len -= token_len;
}
ERR_FAIL_COND_V(total_len > 0, ERR_INVALID_DATA);
return OK;
}
Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code, CompressMode p_compress_mode) {
HashMap<StringName, uint32_t> identifier_map;
HashMap<Variant, uint32_t, VariantHasher, VariantComparator> constant_map;
Vector<uint8_t> token_buffer;
HashMap<uint32_t, uint32_t> token_lines;
HashMap<uint32_t, uint32_t> token_columns;
GDScriptTokenizerText tokenizer;
tokenizer.set_source_code(p_code);
tokenizer.set_multiline_mode(true); // Ignore whitespace tokens.
Token current = tokenizer.scan();
int token_pos = 0;
int last_token_line = 0;
int token_counter = 0;
while (current.type != Token::TK_EOF) {
int token_len = _token_to_binary(current, token_buffer, token_pos, identifier_map, constant_map);
token_pos += token_len;
if (token_counter > 0 && current.start_line > last_token_line) {
token_lines[token_counter] = current.start_line;
token_columns[token_counter] = current.start_column;
}
last_token_line = current.end_line;
current = tokenizer.scan();
token_counter++;
}
// Reverse maps.
Vector<StringName> rev_identifier_map;
rev_identifier_map.resize(identifier_map.size());
for (const KeyValue<StringName, uint32_t> &E : identifier_map) {
rev_identifier_map.write[E.value] = E.key;
}
Vector<Variant> rev_constant_map;
rev_constant_map.resize(constant_map.size());
for (const KeyValue<Variant, uint32_t> &E : constant_map) {
rev_constant_map.write[E.value] = E.key;
}
HashMap<uint32_t, uint32_t> rev_token_lines;
for (const KeyValue<uint32_t, uint32_t> &E : token_lines) {
rev_token_lines[E.value] = E.key;
}
// Remove continuation lines from map.
for (int line : tokenizer.get_continuation_lines()) {
if (rev_token_lines.has(line)) {
token_lines.erase(rev_token_lines[line]);
token_columns.erase(rev_token_lines[line]);
}
}
Vector<uint8_t> contents;
contents.resize(20);
encode_uint32(identifier_map.size(), &contents.write[0]);
encode_uint32(constant_map.size(), &contents.write[4]);
encode_uint32(token_lines.size(), &contents.write[8]);
encode_uint32(token_counter, &contents.write[16]);
int buf_pos = 20;
// Save identifiers.
for (const StringName &id : rev_identifier_map) {
String s = id.operator String();
int len = s.length();
contents.resize(buf_pos + (len + 1) * 4);
encode_uint32(len, &contents.write[buf_pos]);
buf_pos += 4;
for (int i = 0; i < len; i++) {
uint8_t tmp[4];
encode_uint32(s[i], tmp);
for (int b = 0; b < 4; b++) {
contents.write[buf_pos + b] = tmp[b] ^ 0xb6;
}
buf_pos += 4;
}
}
// Save constants.
for (const Variant &v : rev_constant_map) {
int len;
// Objects cannot be constant, never encode objects.
Error err = encode_variant(v, nullptr, len, false);
ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant.");
contents.resize(buf_pos + len);
encode_variant(v, &contents.write[buf_pos], len, false);
buf_pos += len;
}
// Save lines and columns.
contents.resize(buf_pos + token_lines.size() * 16);
for (const KeyValue<uint32_t, uint32_t> &e : token_lines) {
encode_uint32(e.key, &contents.write[buf_pos]);
buf_pos += 4;
encode_uint32(e.value, &contents.write[buf_pos]);
buf_pos += 4;
}
for (const KeyValue<uint32_t, uint32_t> &e : token_columns) {
encode_uint32(e.key, &contents.write[buf_pos]);
buf_pos += 4;
encode_uint32(e.value, &contents.write[buf_pos]);
buf_pos += 4;
}
// Store tokens.
contents.append_array(token_buffer);
Vector<uint8_t> buf;
// Save header.
buf.resize(12);
buf.write[0] = 'G';
buf.write[1] = 'D';
buf.write[2] = 'S';
buf.write[3] = 'C';
encode_uint32(TOKENIZER_VERSION, &buf.write[4]);
switch (p_compress_mode) {
case COMPRESS_NONE:
encode_uint32(0u, &buf.write[8]);
buf.append_array(contents);
break;
case COMPRESS_ZSTD: {
encode_uint32(contents.size(), &buf.write[8]);
Vector<uint8_t> compressed;
int max_size = Compression::get_max_compressed_buffer_size(contents.size(), Compression::MODE_ZSTD);
compressed.resize(max_size);
int compressed_size = Compression::compress(compressed.ptrw(), contents.ptr(), contents.size(), Compression::MODE_ZSTD);
ERR_FAIL_COND_V_MSG(compressed_size < 0, Vector<uint8_t>(), "Error compressing GDScript tokenizer buffer.");
compressed.resize(compressed_size);
buf.append_array(compressed);
} break;
}
return buf;
}
int GDScriptTokenizerBuffer::get_cursor_line() const {
return 0;
}
int GDScriptTokenizerBuffer::get_cursor_column() const {
return 0;
}
void GDScriptTokenizerBuffer::set_cursor_position(int p_line, int p_column) {
}
void GDScriptTokenizerBuffer::set_multiline_mode(bool p_state) {
multiline_mode = p_state;
}
bool GDScriptTokenizerBuffer::is_past_cursor() const {
return false;
}
void GDScriptTokenizerBuffer::push_expression_indented_block() {
indent_stack_stack.push_back(indent_stack);
}
void GDScriptTokenizerBuffer::pop_expression_indented_block() {
ERR_FAIL_COND(indent_stack_stack.is_empty());
indent_stack = indent_stack_stack.back()->get();
indent_stack_stack.pop_back();
}
GDScriptTokenizer::Token GDScriptTokenizerBuffer::scan() {
// Add final newline.
if (current >= tokens.size() && !last_token_was_newline) {
Token newline;
newline.type = Token::NEWLINE;
newline.start_line = current_line;
newline.end_line = current_line;
last_token_was_newline = true;
return newline;
}
// Resolve pending indentation change.
if (pending_indents > 0) {
pending_indents--;
Token indent;
indent.type = Token::INDENT;
indent.start_line = current_line;
indent.end_line = current_line;
return indent;
} else if (pending_indents < 0) {
pending_indents++;
Token dedent;
dedent.type = Token::DEDENT;
dedent.start_line = current_line;
dedent.end_line = current_line;
return dedent;
}
if (current >= tokens.size()) {
if (!indent_stack.is_empty()) {
pending_indents -= indent_stack.size();
indent_stack.clear();
return scan();
}
Token eof;
eof.type = Token::TK_EOF;
return eof;
};
if (!last_token_was_newline && token_lines.has(current)) {
current_line = token_lines[current];
uint32_t current_column = token_columns[current];
// Check if there's a need to indent/dedent.
if (!multiline_mode) {
uint32_t previous_indent = 0;
if (!indent_stack.is_empty()) {
previous_indent = indent_stack.back()->get();
}
if (current_column - 1 > previous_indent) {
pending_indents++;
indent_stack.push_back(current_column - 1);
} else {
while (current_column - 1 < previous_indent) {
pending_indents--;
indent_stack.pop_back();
if (indent_stack.is_empty()) {
break;
}
previous_indent = indent_stack.back()->get();
}
}
Token newline;
newline.type = Token::NEWLINE;
newline.start_line = current_line;
newline.end_line = current_line;
last_token_was_newline = true;
return newline;
}
}
last_token_was_newline = false;
Token token = tokens[current++];
return token;
}

View file

@ -0,0 +1,93 @@
/**************************************************************************/
/* gdscript_tokenizer_buffer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_TOKENIZER_BUFFER_H
#define GDSCRIPT_TOKENIZER_BUFFER_H
#include "gdscript_tokenizer.h"
class GDScriptTokenizerBuffer : public GDScriptTokenizer {
public:
enum CompressMode {
COMPRESS_NONE,
COMPRESS_ZSTD,
};
enum {
TOKEN_BYTE_MASK = 0x80,
TOKEN_BITS = 8,
TOKEN_MASK = (1 << (TOKEN_BITS - 1)) - 1,
};
Vector<StringName> identifiers;
Vector<Variant> constants;
Vector<int> continuation_lines;
HashMap<int, int> token_lines;
HashMap<int, int> token_columns;
Vector<Token> tokens;
int current = 0;
uint32_t current_line = 1;
bool multiline_mode = false;
List<int> indent_stack;
List<List<int>> indent_stack_stack; // For lambdas, which require manipulating the indentation point.
int pending_indents = 0;
bool last_token_was_newline = false;
#ifdef TOOLS_ENABLED
HashMap<int, CommentData> dummy;
#endif // TOOLS_ENABLED
static int _token_to_binary(const Token &p_token, Vector<uint8_t> &r_buffer, int p_start, HashMap<StringName, uint32_t> &r_identifiers_map, HashMap<Variant, uint32_t, VariantHasher, VariantComparator> &r_constants_map);
Token _binary_to_token(const uint8_t *p_buffer);
public:
Error set_code_buffer(const Vector<uint8_t> &p_buffer);
static Vector<uint8_t> parse_code_string(const String &p_code, CompressMode p_compress_mode);
virtual int get_cursor_line() const override;
virtual int get_cursor_column() const override;
virtual void set_cursor_position(int p_line, int p_column) override;
virtual void set_multiline_mode(bool p_state) override;
virtual bool is_past_cursor() const override;
virtual void push_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual void pop_expression_indented_block() override; // For lambdas, or blocks inside expressions.
virtual bool is_text() override { return false; };
#ifdef TOOLS_ENABLED
virtual const HashMap<int, CommentData> &get_comments() const override {
return dummy;
}
#endif // TOOLS_ENABLED
virtual Token scan() override;
};
#endif // GDSCRIPT_TOKENIZER_BUFFER_H

View file

@ -0,0 +1,126 @@
/**************************************************************************/
/* gdscript_utility_callable.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_callable.h"
#include "core/templates/hashfuncs.h"
bool GDScriptUtilityCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
return p_a->hash() == p_b->hash();
}
bool GDScriptUtilityCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
return p_a->hash() < p_b->hash();
}
uint32_t GDScriptUtilityCallable::hash() const {
return h;
}
String GDScriptUtilityCallable::get_as_text() const {
String scope;
switch (type) {
case TYPE_INVALID:
scope = "<invalid scope>";
break;
case TYPE_GLOBAL:
scope = "@GlobalScope";
break;
case TYPE_GDSCRIPT:
scope = "@GDScript";
break;
}
return vformat("%s::%s (Callable)", scope, function_name);
}
CallableCustom::CompareEqualFunc GDScriptUtilityCallable::get_compare_equal_func() const {
return compare_equal;
}
CallableCustom::CompareLessFunc GDScriptUtilityCallable::get_compare_less_func() const {
return compare_less;
}
bool GDScriptUtilityCallable::is_valid() const {
return type != TYPE_INVALID;
}
StringName GDScriptUtilityCallable::get_method() const {
return function_name;
}
ObjectID GDScriptUtilityCallable::get_object() const {
return ObjectID();
}
int GDScriptUtilityCallable::get_argument_count(bool &r_is_valid) const {
switch (type) {
case TYPE_INVALID:
r_is_valid = false;
return 0;
case TYPE_GLOBAL:
r_is_valid = true;
return Variant::get_utility_function_argument_count(function_name);
case TYPE_GDSCRIPT:
r_is_valid = true;
return GDScriptUtilityFunctions::get_function_argument_count(function_name);
}
ERR_FAIL_V_MSG(0, "Invalid type.");
}
void GDScriptUtilityCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
switch (type) {
case TYPE_INVALID:
r_return_value = vformat(R"(Trying to call invalid utility function "%s".)", function_name);
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
r_call_error.argument = 0;
r_call_error.expected = 0;
break;
case TYPE_GLOBAL:
Variant::call_utility_function(function_name, &r_return_value, p_arguments, p_argcount, r_call_error);
break;
case TYPE_GDSCRIPT:
gdscript_function(&r_return_value, p_arguments, p_argcount, r_call_error);
break;
}
}
GDScriptUtilityCallable::GDScriptUtilityCallable(const StringName &p_function_name) {
function_name = p_function_name;
if (GDScriptUtilityFunctions::function_exists(p_function_name)) {
type = TYPE_GDSCRIPT;
gdscript_function = GDScriptUtilityFunctions::get_function(p_function_name);
} else if (Variant::has_utility_function(p_function_name)) {
type = TYPE_GLOBAL;
} else {
ERR_PRINT(vformat(R"(Unknown utility function "%s".)", p_function_name));
}
h = p_function_name.hash();
}

View file

@ -0,0 +1,66 @@
/**************************************************************************/
/* gdscript_utility_callable.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_CALLABLE_H
#define GDSCRIPT_UTILITY_CALLABLE_H
#include "gdscript_utility_functions.h"
#include "core/variant/callable.h"
class GDScriptUtilityCallable : public CallableCustom {
StringName function_name;
enum Type {
TYPE_INVALID,
TYPE_GLOBAL,
TYPE_GDSCRIPT,
};
Type type = TYPE_INVALID;
GDScriptUtilityFunctions::FunctionPtr gdscript_function = nullptr;
uint32_t h = 0;
static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
public:
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;
bool is_valid() const override;
StringName get_method() const override;
ObjectID get_object() const override;
int get_argument_count(bool &r_is_valid) const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
GDScriptUtilityCallable(const StringName &p_function_name);
};
#endif // GDSCRIPT_UTILITY_CALLABLE_H

View file

@ -0,0 +1,798 @@
/**************************************************************************/
/* gdscript_utility_functions.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "gdscript.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 "core/variant/typed_array.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.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.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 {
#ifndef DISABLE_DEPRECATED
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);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type)));
}
}
}
#endif // DISABLE_DEPRECATED
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 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.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_ret = RTR("Cannot resize array.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
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_ret = RTR("Cannot resize array.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
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 = Math::division_round_up(to - from, incr);
} else {
count = Math::division_round_up(from - to, -incr);
}
Error err = arr.resize(count);
if (err != OK) {
*r_ret = RTR("Cannot resize array.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
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.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 inst_to_dict(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_error.expected = Variant::OBJECT;
*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();
String path = p->get_script_path();
Vector<StringName> sname;
while (p->_owner) {
sname.push_back(p->local_name);
p = p->_owner;
}
sname.reverse();
if (!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"] = path;
for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
if (!d.has(E.key)) {
d[E.key] = ins->members[E.value.index];
}
}
*r_ret = d;
}
}
}
static inline void dict_to_inst(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 = RTR("Cannot instantiate GDScript class.");
return;
}
GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
Ref<GDScript> gd_ref = ins->get_script();
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
ins->members.write[E.value.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.expected = 3;
*r_ret = Variant();
return;
}
if (p_arg_count > 4) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
r_error.expected = 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 s;
for (int i = 0; i < p_arg_count; i++) {
s += p_args[i]->operator String();
}
if (Thread::get_caller_id() == Thread::get_main_id()) {
ScriptLanguage *script = GDScriptLanguage::get_singleton();
if (script->debug_get_stack_level_count() > 0) {
s += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()";
}
} else {
s += "\n At: Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id());
}
print_line(s);
*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);
if (Thread::get_caller_id() != Thread::get_main_id()) {
print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()));
return;
}
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) + "'");
};
*r_ret = Variant();
}
static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(0);
if (Thread::get_caller_id() != Thread::get_main_id()) {
*r_ret = TypedArray<Dictionary>();
return;
}
ScriptLanguage *script = GDScriptLanguage::get_singleton();
TypedArray<Dictionary> 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:
case Variant::STRING_NAME: {
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;
case Variant::PACKED_VECTOR4_ARRAY: {
Vector<Vector4> 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()));
}
}
}
static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
VALIDATE_ARG_COUNT(2);
if (p_args[1]->get_type() == Variant::INT) {
int builtin_type = *p_args[1];
if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
*r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::NIL;
return;
}
*r_ret = p_args[0]->get_type() == builtin_type;
return;
}
bool was_type_freed = false;
Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
if (was_type_freed) {
*r_ret = RTR("Type argument is a previously freed instance.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::NIL;
return;
}
if (!type_object) {
*r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::NIL;
return;
}
bool was_value_freed = false;
Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
if (was_value_freed) {
*r_ret = RTR("Value argument is a previously freed instance.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
return;
}
if (!value_object) {
*r_ret = false;
return;
}
GDScriptNativeClass *native_type = Object::cast_to<GDScriptNativeClass>(type_object);
if (native_type) {
*r_ret = ClassDB::is_parent_class(value_object->get_class_name(), native_type->get_name());
return;
}
Script *script_type = Object::cast_to<Script>(type_object);
if (script_type) {
bool result = false;
if (value_object->get_script_instance()) {
Script *script_ptr = value_object->get_script_instance()->get_script().ptr();
while (script_ptr) {
if (script_ptr == script_type) {
result = true;
break;
}
script_ptr = script_ptr->get_base_script().ptr();
}
}
*r_ret = result;
return;
}
*r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::NIL;
}
};
struct GDScriptUtilityFunctionInfo {
GDScriptUtilityFunctions::FunctionPtr function = nullptr;
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() {
#ifndef DISABLE_DEPRECATED
REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
#endif // DISABLE_DEPRECATED
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(range, false, Variant::ARRAY);
REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
REGISTER_FUNC(dict_to_inst, 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"));
REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
}
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_NULL_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_NULL_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_NULL_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_NULL_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_NULL_V(info, Variant::NIL);
ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL);
return info->info.arguments.get(p_arg).type;
}
int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_NULL_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_NULL_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_NULL_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 StringName &E : utility_function_name_table) {
r_functions->push_back(E);
}
}
MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) {
GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function);
ERR_FAIL_NULL_V(info, MethodInfo());
return info->info;
}

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* gdscript_utility_functions.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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"
template <typename T>
class TypedArray;
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);
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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,268 @@
/**************************************************************************/
/* gdscript_warning.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gdscript_warning.h"
#include "core/variant/variant.h"
#ifdef DEBUG_ENABLED
String GDScriptWarning::get_message() const {
#define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String());
switch (code) {
case UNASSIGNED_VARIABLE:
CHECK_SYMBOLS(1);
return vformat(R"(The variable "%s" was used before being assigned a value.)", symbols[0]);
case UNASSIGNED_VARIABLE_OP_ASSIGN:
CHECK_SYMBOLS(1);
return vformat(R"(Using assignment with operation but the variable "%s" was not previously assigned a value.)", symbols[0]);
case UNUSED_VARIABLE:
CHECK_SYMBOLS(1);
return vformat(R"(The local variable "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)", symbols[0], symbols[0]);
case UNUSED_LOCAL_CONSTANT:
CHECK_SYMBOLS(1);
return vformat(R"(The local constant "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)", symbols[0], symbols[0]);
case UNUSED_PRIVATE_CLASS_VARIABLE:
CHECK_SYMBOLS(1);
return vformat(R"(The class variable "%s" is declared but never used in the class.)", symbols[0]);
case UNUSED_PARAMETER:
CHECK_SYMBOLS(2);
return vformat(R"*(The parameter "%s" is never used in the function "%s()". If this is intended, prefix it with an underscore: "_%s".)*", symbols[1], symbols[0], symbols[1]);
case UNUSED_SIGNAL:
CHECK_SYMBOLS(1);
return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]);
case SHADOWED_VARIABLE:
CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_VARIABLE_BASE_CLASS:
CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_GLOBAL_IDENTIFIER:
CHECK_SYMBOLS(3);
return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
case UNREACHABLE_CODE:
CHECK_SYMBOLS(1);
return vformat(R"*(Unreachable code (statement after return) in function "%s()".)*", symbols[0]);
case UNREACHABLE_PATTERN:
return "Unreachable pattern (pattern after wildcard or bind).";
case STANDALONE_EXPRESSION:
return "Standalone expression (the line may have no effect).";
case STANDALONE_TERNARY:
return "Standalone ternary operator: the return value is being discarded.";
case INCOMPATIBLE_TERNARY:
return "Values of the ternary operator are not mutually compatible.";
case UNTYPED_DECLARATION:
CHECK_SYMBOLS(2);
if (symbols[0] == "Function") {
return vformat(R"*(%s "%s()" has no static return type.)*", symbols[0], symbols[1]);
}
return vformat(R"(%s "%s" has no static type.)", symbols[0], symbols[1]);
case INFERRED_DECLARATION:
CHECK_SYMBOLS(2);
return vformat(R"(%s "%s" has an implicitly inferred static type.)", symbols[0], symbols[1]);
case UNSAFE_PROPERTY_ACCESS:
CHECK_SYMBOLS(2);
return vformat(R"(The property "%s" is not present on the inferred type "%s" (but may be present on a subtype).)", symbols[0], symbols[1]);
case UNSAFE_METHOD_ACCESS:
CHECK_SYMBOLS(2);
return vformat(R"*(The method "%s()" is not present on the inferred type "%s" (but may be present on a subtype).)*", symbols[0], symbols[1]);
case UNSAFE_CAST:
CHECK_SYMBOLS(1);
return vformat(R"(Casting "Variant" to "%s" is unsafe.)", symbols[0]);
case UNSAFE_CALL_ARGUMENT:
CHECK_SYMBOLS(5);
return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
case UNSAFE_VOID_RETURN:
CHECK_SYMBOLS(2);
return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]);
case RETURN_VALUE_DISCARDED:
CHECK_SYMBOLS(1);
return vformat(R"*(The function "%s()" returns a value that will be discarded if not used.)*", symbols[0]);
case STATIC_CALLED_ON_INSTANCE:
CHECK_SYMBOLS(2);
return vformat(R"*(The function "%s()" is a static function but was called from an instance. Instead, it should be directly called from the type: "%s.%s()".)*", symbols[0], symbols[1], symbols[0]);
case REDUNDANT_STATIC_UNLOAD:
return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
case REDUNDANT_AWAIT:
return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)";
case ASSERT_ALWAYS_TRUE:
return "Assert statement is redundant because the expression is always true.";
case ASSERT_ALWAYS_FALSE:
return "Assert statement will raise an error because the expression is always false.";
case INTEGER_DIVISION:
return "Integer division, decimal part will be discarded.";
case NARROWING_CONVERSION:
return "Narrowing conversion (float is converted to int and loses precision).";
case INT_AS_ENUM_WITHOUT_CAST:
return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type.";
case INT_AS_ENUM_WITHOUT_MATCH:
CHECK_SYMBOLS(3);
return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]);
case ENUM_VARIABLE_WITHOUT_DEFAULT:
CHECK_SYMBOLS(1);
return vformat(R"(The variable "%s" has an enum type and does not set an explicit default value. The default will be set to "0".)", symbols[0]);
case EMPTY_FILE:
return "Empty script file.";
case DEPRECATED_KEYWORD:
CHECK_SYMBOLS(2);
return vformat(R"(The "%s" keyword is deprecated and will be removed in a future release, please replace its uses by "%s".)", symbols[0], symbols[1]);
case RENAMED_IN_GODOT_4_HINT:
break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here.
case CONFUSABLE_IDENTIFIER:
CHECK_SYMBOLS(1);
return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
case CONFUSABLE_LOCAL_DECLARATION:
CHECK_SYMBOLS(2);
return vformat(R"(The %s "%s" is declared below in the parent block.)", symbols[0], symbols[1]);
case CONFUSABLE_LOCAL_USAGE:
CHECK_SYMBOLS(1);
return vformat(R"(The identifier "%s" will be shadowed below in the block.)", symbols[0]);
case CONFUSABLE_CAPTURE_REASSIGNMENT:
CHECK_SYMBOLS(1);
return vformat(R"(Reassigning lambda capture does not modify the outer local variable "%s".)", symbols[0]);
case INFERENCE_ON_VARIANT:
CHECK_SYMBOLS(1);
return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]);
case NATIVE_METHOD_OVERRIDE:
CHECK_SYMBOLS(2);
return vformat(R"*(The method "%s()" overrides a method from native class "%s". This won't be called by the engine and may not work as expected.)*", symbols[0], symbols[1]);
case GET_NODE_DEFAULT_WITHOUT_ONREADY:
CHECK_SYMBOLS(1);
return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]);
case ONREADY_WITH_EXPORT:
return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
#ifndef DISABLE_DEPRECATED
// Never produced. These warnings migrated from 3.x by mistake.
case PROPERTY_USED_AS_FUNCTION: // There is already an error.
case CONSTANT_USED_AS_FUNCTION: // There is already an error.
case FUNCTION_USED_AS_PROPERTY: // This is valid, returns `Callable`.
break;
#endif
case WARNING_MAX:
break; // Can't happen, but silences warning.
}
ERR_FAIL_V_MSG(String(), vformat(R"(Invalid GDScript warning "%s".)", get_name_from_code(code)));
#undef CHECK_SYMBOLS
}
int GDScriptWarning::get_default_value(Code p_code) {
ERR_FAIL_INDEX_V_MSG(p_code, WARNING_MAX, WarnLevel::IGNORE, "Getting default value of invalid warning code.");
return default_warning_levels[p_code];
}
PropertyInfo GDScriptWarning::get_property_info(Code p_code) {
// Making this a separate function in case a warning needs different PropertyInfo in the future.
if (p_code == Code::RENAMED_IN_GODOT_4_HINT) {
return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code));
}
return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error");
}
String GDScriptWarning::get_name() const {
return get_name_from_code(code);
}
String GDScriptWarning::get_name_from_code(Code p_code) {
ERR_FAIL_COND_V(p_code < 0 || p_code >= WARNING_MAX, String());
static const char *names[] = {
"UNASSIGNED_VARIABLE",
"UNASSIGNED_VARIABLE_OP_ASSIGN",
"UNUSED_VARIABLE",
"UNUSED_LOCAL_CONSTANT",
"UNUSED_PRIVATE_CLASS_VARIABLE",
"UNUSED_PARAMETER",
"UNUSED_SIGNAL",
"SHADOWED_VARIABLE",
"SHADOWED_VARIABLE_BASE_CLASS",
"SHADOWED_GLOBAL_IDENTIFIER",
"UNREACHABLE_CODE",
"UNREACHABLE_PATTERN",
"STANDALONE_EXPRESSION",
"STANDALONE_TERNARY",
"INCOMPATIBLE_TERNARY",
"UNTYPED_DECLARATION",
"INFERRED_DECLARATION",
"UNSAFE_PROPERTY_ACCESS",
"UNSAFE_METHOD_ACCESS",
"UNSAFE_CAST",
"UNSAFE_CALL_ARGUMENT",
"UNSAFE_VOID_RETURN",
"RETURN_VALUE_DISCARDED",
"STATIC_CALLED_ON_INSTANCE",
"REDUNDANT_STATIC_UNLOAD",
"REDUNDANT_AWAIT",
"ASSERT_ALWAYS_TRUE",
"ASSERT_ALWAYS_FALSE",
"INTEGER_DIVISION",
"NARROWING_CONVERSION",
"INT_AS_ENUM_WITHOUT_CAST",
"INT_AS_ENUM_WITHOUT_MATCH",
"ENUM_VARIABLE_WITHOUT_DEFAULT",
"EMPTY_FILE",
"DEPRECATED_KEYWORD",
"RENAMED_IN_GODOT_4_HINT",
"CONFUSABLE_IDENTIFIER",
"CONFUSABLE_LOCAL_DECLARATION",
"CONFUSABLE_LOCAL_USAGE",
"CONFUSABLE_CAPTURE_REASSIGNMENT",
"INFERENCE_ON_VARIANT",
"NATIVE_METHOD_OVERRIDE",
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
"ONREADY_WITH_EXPORT",
#ifndef DISABLE_DEPRECATED
"PROPERTY_USED_AS_FUNCTION",
"CONSTANT_USED_AS_FUNCTION",
"FUNCTION_USED_AS_PROPERTY",
#endif
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
return names[(int)p_code];
}
String GDScriptWarning::get_settings_path_from_code(Code p_code) {
return "debug/gdscript/warnings/" + get_name_from_code(p_code).to_lower();
}
GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name) {
for (int i = 0; i < WARNING_MAX; i++) {
if (get_name_from_code((Code)i) == p_name) {
return (Code)i;
}
}
return WARNING_MAX;
}
#endif // DEBUG_ENABLED

View file

@ -0,0 +1,171 @@
/**************************************************************************/
/* gdscript_warning.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_WARNING_H
#define GDSCRIPT_WARNING_H
#ifdef DEBUG_ENABLED
#include "core/object/object.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"
class GDScriptWarning {
public:
enum WarnLevel {
IGNORE,
WARN,
ERROR
};
enum Code {
UNASSIGNED_VARIABLE, // Variable used but never assigned.
UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc).
UNUSED_VARIABLE, // Local variable is declared but never used.
UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used.
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
UNUSED_PARAMETER, // Function parameter is never used.
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class.
SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
UNREACHABLE_CODE, // Code after a return statement.
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).
STANDALONE_EXPRESSION, // Expression not assigned to a variable.
STANDALONE_TERNARY, // Return value of ternary expression is discarded.
INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible.
UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or implicitly inferred.
INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type.
UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes).
UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes).
UNSAFE_CAST, // Casting a `Variant` value to non-`Variant`.
UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the required type.
UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false.
INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded.
NARROWING_CONVERSION, // Float value into an integer slot, precision is lost.
INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting.
INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
ENUM_VARIABLE_WITHOUT_DEFAULT, // A variable with an enum type does not have a default value. The default will be set to `0` instead of the first enum value.
EMPTY_FILE, // A script file is empty.
DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced.
RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4.
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
CONFUSABLE_LOCAL_DECLARATION, // The parent block declares an identifier with the same name below.
CONFUSABLE_LOCAL_USAGE, // The identifier will be shadowed below in the block.
CONFUSABLE_CAPTURE_REASSIGNMENT, // Reassigning lambda capture does not modify the outer local variable.
INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant.
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
#ifndef DISABLE_DEPRECATED
PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name.
CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name.
FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name.
#endif
WARNING_MAX,
};
constexpr static WarnLevel default_warning_levels[] = {
WARN, // UNASSIGNED_VARIABLE
WARN, // UNASSIGNED_VARIABLE_OP_ASSIGN
WARN, // UNUSED_VARIABLE
WARN, // UNUSED_LOCAL_CONSTANT
WARN, // UNUSED_PRIVATE_CLASS_VARIABLE
WARN, // UNUSED_PARAMETER
WARN, // UNUSED_SIGNAL
WARN, // SHADOWED_VARIABLE
WARN, // SHADOWED_VARIABLE_BASE_CLASS
WARN, // SHADOWED_GLOBAL_IDENTIFIER
WARN, // UNREACHABLE_CODE
WARN, // UNREACHABLE_PATTERN
WARN, // STANDALONE_EXPRESSION
WARN, // STANDALONE_TERNARY
WARN, // INCOMPATIBLE_TERNARY
IGNORE, // UNTYPED_DECLARATION // Static typing is optional, we don't want to spam warnings.
IGNORE, // INFERRED_DECLARATION // Static typing is optional, we don't want to spam warnings.
IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios.
IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios.
IGNORE, // UNSAFE_CAST // Too common in untyped scenarios.
IGNORE, // UNSAFE_CALL_ARGUMENT // Too common in untyped scenarios.
WARN, // UNSAFE_VOID_RETURN
IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.).
WARN, // STATIC_CALLED_ON_INSTANCE
WARN, // REDUNDANT_STATIC_UNLOAD
WARN, // REDUNDANT_AWAIT
WARN, // ASSERT_ALWAYS_TRUE
WARN, // ASSERT_ALWAYS_FALSE
WARN, // INTEGER_DIVISION
WARN, // NARROWING_CONVERSION
WARN, // INT_AS_ENUM_WITHOUT_CAST
WARN, // INT_AS_ENUM_WITHOUT_MATCH
WARN, // ENUM_VARIABLE_WITHOUT_DEFAULT
WARN, // EMPTY_FILE
WARN, // DEPRECATED_KEYWORD
WARN, // RENAMED_IN_GODOT_4_HINT
WARN, // CONFUSABLE_IDENTIFIER
WARN, // CONFUSABLE_LOCAL_DECLARATION
WARN, // CONFUSABLE_LOCAL_USAGE
WARN, // CONFUSABLE_CAPTURE_REASSIGNMENT
ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type.
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
#ifndef DISABLE_DEPRECATED
WARN, // PROPERTY_USED_AS_FUNCTION
WARN, // CONSTANT_USED_AS_FUNCTION
WARN, // FUNCTION_USED_AS_PROPERTY
#endif
};
static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
Code code = WARNING_MAX;
int start_line = -1, end_line = -1;
int leftmost_column = -1, rightmost_column = -1;
Vector<String> symbols;
String get_name() const;
String get_message() const;
static int get_default_value(Code p_code);
static PropertyInfo get_property_info(Code p_code);
static String get_name_from_code(Code p_code);
static String get_settings_path_from_code(Code p_code);
static Code get_code_from_name(const String &p_name);
};
#endif // DEBUG_ENABLED
#endif // GDSCRIPT_WARNING_H

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z"/></svg>

After

Width:  |  Height:  |  Size: 499 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" stroke="#e0e0e0" d="m7 1-.565 2.258a5 5 0 0 0-.689.28L3.758 2.343 2.344 3.758l1.195 1.994a5 5 0 0 0-.285.685L1 7v2l2.258.564a5 5 0 0 0 .279.688l-1.193 1.99 1.414 1.414 1.994-1.195a5 5 0 0 0 .685.285L7 15h2l.564-2.258a5 5 0 0 0 .688-.28l1.99 1.194 1.414-1.414-1.195-1.994a5 5 0 0 0 .285-.685L15 9V7l-2.258-.564a5 5 0 0 0-.28-.688l1.194-1.99-1.414-1.414-1.994 1.195a5 5 0 0 0-.686-.285L9 1H7zm1 5a2 2 0 0 1 0 4 2 2 0 0 1 0-4z"/></svg>

After

Width:  |  Height:  |  Size: 513 B

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,173 @@
/**************************************************************************/
/* gdscript_extend_parser.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_EXTEND_PARSER_H
#define GDSCRIPT_EXTEND_PARSER_H
#include "../gdscript_parser.h"
#include "godot_lsp.h"
#include "core/variant/variant.h"
#ifndef LINE_NUMBER_TO_INDEX
#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
#endif
#ifndef COLUMN_NUMBER_TO_INDEX
#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1)
#endif
#ifndef SYMBOL_SEPERATOR
#define SYMBOL_SEPERATOR "::"
#endif
#ifndef JOIN_SYMBOLS
#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPERATOR + (name))
#endif
typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers;
/**
* Represents a Position as used by GDScript Parser. Used for conversion to and from `lsp::Position`.
*
* Difference to `lsp::Position`:
* * Line & Char/column: 1-based
* * LSP: both 0-based
* * Tabs are expanded to columns using tab size (`text_editor/behavior/indent/size`).
* * LSP: tab is single char
*
* Example:
* ```gdscript
* var my_value = 42
* ```
* `_` is at:
* * Godot: `column=12`
* * using `indent/size=4`
* * Note: counting starts at `1`
* * LSP: `character=8`
* * Note: counting starts at `0`
*/
struct GodotPosition {
int line;
int column;
GodotPosition(int p_line, int p_column) :
line(p_line), column(p_column) {}
lsp::Position to_lsp(const Vector<String> &p_lines) const;
static GodotPosition from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines);
bool operator==(const GodotPosition &p_other) const {
return line == p_other.line && column == p_other.column;
}
String to_string() const {
return vformat("(%d,%d)", line, column);
}
};
struct GodotRange {
GodotPosition start;
GodotPosition end;
GodotRange(GodotPosition p_start, GodotPosition p_end) :
start(p_start), end(p_end) {}
lsp::Range to_lsp(const Vector<String> &p_lines) const;
static GodotRange from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines);
bool operator==(const GodotRange &p_other) const {
return start == p_other.start && end == p_other.end;
}
String to_string() const {
return vformat("[%s:%s]", start.to_string(), end.to_string());
}
};
class ExtendGDScriptParser : public GDScriptParser {
String path;
Vector<String> lines;
lsp::DocumentSymbol class_symbol;
Vector<lsp::Diagnostic> diagnostics;
List<lsp::DocumentLink> document_links;
ClassMembers members;
HashMap<String, ClassMembers> inner_classes;
lsp::Range range_of_node(const GDScriptParser::Node *p_node) const;
void update_diagnostics();
void update_symbols();
void update_document_links(const String &p_code);
void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol);
void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol);
Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const;
Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const;
const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name = "") const;
Array member_completions;
public:
_FORCE_INLINE_ const String &get_path() const { return path; }
_FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; }
_FORCE_INLINE_ const lsp::DocumentSymbol &get_symbols() const { return class_symbol; }
_FORCE_INLINE_ const Vector<lsp::Diagnostic> &get_diagnostics() const { return diagnostics; }
_FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
_FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const;
String get_text_for_completion(const lsp::Position &p_cursor) const;
String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const;
String get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const;
String get_uri() const;
/**
* `p_symbol_name` gets ignored if empty. Otherwise symbol must match passed in named.
*
* Necessary when multiple symbols at same line for example with `func`:
* `func handle_arg(arg: int):`
* -> Without `p_symbol_name`: returns `handle_arg`. Even if parameter (`arg`) is wanted.
* With `p_symbol_name`: symbol name MUST match `p_symbol_name`: returns `arg`.
*/
const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line, const String &p_symbol_name = "") const;
const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const;
const List<lsp::DocumentLink> &get_document_links() const;
const Array &get_member_completions();
Dictionary generate_api() const;
Error parse(const String &p_code, const String &p_path);
};
#endif // GDSCRIPT_EXTEND_PARSER_H

View file

@ -0,0 +1,348 @@
/**************************************************************************/
/* gdscript_language_protocol.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_language_protocol.h"
#include "core/config/project_settings.h"
#include "editor/doc_tools.h"
#include "editor/editor_help.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = nullptr;
Error GDScriptLanguageProtocol::LSPeer::handle_data() {
int read = 0;
// Read headers
if (!has_header) {
while (true) {
if (req_pos >= LSP_MAX_BUFFER_SIZE) {
req_pos = 0;
ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Response header too big");
}
Error err = connection->get_partial_data(&req_buf[req_pos], 1, read);
if (err != OK) {
return FAILED;
} else if (read != 1) { // Busy, wait until next poll
return ERR_BUSY;
}
char *r = (char *)req_buf;
int l = req_pos;
// End of headers
if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
r[l - 3] = '\0'; // Null terminate to read string
String header;
header.parse_utf8(r);
content_length = header.substr(16).to_int();
has_header = true;
req_pos = 0;
break;
}
req_pos++;
}
}
if (has_header) {
while (req_pos < content_length) {
if (req_pos >= LSP_MAX_BUFFER_SIZE) {
req_pos = 0;
has_header = false;
ERR_FAIL_COND_V_MSG(req_pos >= LSP_MAX_BUFFER_SIZE, ERR_OUT_OF_MEMORY, "Response content too big");
}
Error err = connection->get_partial_data(&req_buf[req_pos], 1, read);
if (err != OK) {
return FAILED;
} else if (read != 1) {
return ERR_BUSY;
}
req_pos++;
}
// Parse data
String msg;
msg.parse_utf8((const char *)req_buf, req_pos);
// Reset to read again
req_pos = 0;
has_header = false;
// Response
String output = GDScriptLanguageProtocol::get_singleton()->process_message(msg);
if (!output.is_empty()) {
res_queue.push_back(output.utf8());
}
}
return OK;
}
Error GDScriptLanguageProtocol::LSPeer::send_data() {
int sent = 0;
while (!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);
if (err != OK) {
return err;
}
res_sent += sent;
}
// Response sent
if (res_sent >= c_res.size() - 1) {
res_sent = 0;
res_queue.remove_at(0);
}
}
return OK;
}
Error GDScriptLanguageProtocol::on_client_connected() {
Ref<StreamPeerTCP> tcp_peer = server->take_connection();
ERR_FAIL_COND_V_MSG(clients.size() >= LSP_MAX_CLIENTS, FAILED, "Max client limits reached");
Ref<LSPeer> peer = memnew(LSPeer);
peer->connection = tcp_peer;
clients.insert(next_client_id, peer);
next_client_id++;
EditorNode::get_log()->add_message("[LSP] Connection Taken", EditorLog::MSG_TYPE_EDITOR);
return OK;
}
void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) {
clients.erase(p_client_id);
EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR);
}
String GDScriptLanguageProtocol::process_message(const String &p_text) {
String ret = process_string(p_text);
if (ret.is_empty()) {
return ret;
} else {
return format_output(ret);
}
}
String GDScriptLanguageProtocol::format_output(const String &p_text) {
String header = "Content-Length: ";
CharString charstr = p_text.utf8();
size_t len = charstr.length();
header += itos(len);
header += "\r\n\r\n";
return header + p_text;
}
void GDScriptLanguageProtocol::_bind_methods() {
ClassDB::bind_method(D_METHOD("initialize", "params"), &GDScriptLanguageProtocol::initialize);
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", "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);
ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized);
}
Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
lsp::InitializeResult ret;
String root_uri = p_params["rootUri"];
String root = p_params["rootPath"];
bool is_same_workspace;
#ifndef WINDOWS_ENABLED
is_same_workspace = root.to_lower() == workspace->root.to_lower();
#else
is_same_workspace = root.replace("\\", "/").to_lower() == workspace->root.to_lower();
#endif
if (root_uri.length() && is_same_workspace) {
workspace->root_uri = root_uri;
} else {
String r_root = workspace->root;
r_root = r_root.lstrip("/");
workspace->root_uri = "file:///" + r_root;
Dictionary params;
params["path"] = workspace->root;
Dictionary request = make_notification("gdscript_client/changeWorkspace", params);
ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(),
vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id));
Ref<LSPeer> peer = clients.get(latest_client_id);
if (peer != nullptr) {
String msg = Variant(request).to_json_string();
msg = format_output(msg);
(*peer)->res_queue.push_back(msg.utf8());
}
}
if (!_initialized) {
workspace->initialize();
text_document->initialize();
_initialized = true;
}
return ret.to_json();
}
void GDScriptLanguageProtocol::initialized(const Variant &p_params) {
lsp::GodotCapabilities capabilities;
DocTools *doc = EditorHelp::get_doc_data();
for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {
lsp::GodotNativeClassInfo gdclass;
gdclass.name = E.value.name;
gdclass.class_doc = &(E.value);
if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E.value.name))) {
gdclass.class_info = ptr;
}
capabilities.native_classes.push_back(gdclass);
}
notify_client("gdscript/capabilities", capabilities.to_json());
}
void GDScriptLanguageProtocol::poll(int p_limit_usec) {
uint64_t target_ticks = OS::get_singleton()->get_ticks_usec() + p_limit_usec;
if (server->is_connection_available()) {
on_client_connected();
}
HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin();
while (E != clients.end()) {
Ref<LSPeer> peer = E->value;
peer->connection->poll();
StreamPeerTCP::Status status = peer->connection->get_status();
if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) {
on_client_disconnected(E->key);
E = clients.begin();
continue;
} else {
Error err = OK;
while (peer->connection->get_available_bytes() > 0) {
latest_client_id = E->key;
err = peer->handle_data();
if (err != OK || OS::get_singleton()->get_ticks_usec() >= target_ticks) {
break;
}
}
if (err != OK && err != ERR_BUSY) {
on_client_disconnected(E->key);
E = clients.begin();
continue;
}
err = peer->send_data();
if (err != OK && err != ERR_BUSY) {
on_client_disconnected(E->key);
E = clients.begin();
continue;
}
}
++E;
}
}
Error GDScriptLanguageProtocol::start(int p_port, const IPAddress &p_bind_ip) {
return server->listen(p_port, p_bind_ip);
}
void GDScriptLanguageProtocol::stop() {
for (const KeyValue<int, Ref<LSPeer>> &E : clients) {
Ref<LSPeer> peer = clients.get(E.key);
peer->connection->disconnect_from_host();
}
server->stop();
}
void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client_id) {
#ifdef TESTS_ENABLED
if (clients.is_empty()) {
return;
}
#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
p_client_id = latest_client_id;
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
ERR_FAIL_NULL(peer);
Dictionary message = make_notification(p_method, p_params);
String msg = Variant(message).to_json_string();
msg = format_output(msg);
peer->res_queue.push_back(msg.utf8());
}
void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) {
#ifdef TESTS_ENABLED
if (clients.is_empty()) {
return;
}
#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
p_client_id = latest_client_id;
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
ERR_FAIL_NULL(peer);
Dictionary message = make_request(p_method, p_params, next_server_id);
next_server_id++;
String msg = Variant(message).to_json_string();
msg = format_output(msg);
peer->res_queue.push_back(msg.utf8());
}
bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const {
return bool(_EDITOR_GET("network/language_server/enable_smart_resolve"));
}
bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const {
return bool(_EDITOR_GET("network/language_server/show_native_symbols_in_editor"));
}
GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
server.instantiate();
singleton = this;
workspace.instantiate();
text_document.instantiate();
set_scope("textDocument", text_document.ptr());
set_scope("completionItem", text_document.ptr());
set_scope("workspace", workspace.ptr());
workspace->root = ProjectSettings::get_singleton()->get_resource_path();
}

View file

@ -0,0 +1,121 @@
/**************************************************************************/
/* gdscript_language_protocol.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_LANGUAGE_PROTOCOL_H
#define GDSCRIPT_LANGUAGE_PROTOCOL_H
#include "gdscript_text_document.h"
#include "gdscript_workspace.h"
#include "godot_lsp.h"
#include "core/io/stream_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
#include "modules/modules_enabled.gen.h" // For jsonrpc.
#ifdef MODULE_JSONRPC_ENABLED
#include "modules/jsonrpc/jsonrpc.h"
#else
#error "Can't build GDScript LSP without JSONRPC module."
#endif
#define LSP_MAX_BUFFER_SIZE 4194304
#define LSP_MAX_CLIENTS 8
class GDScriptLanguageProtocol : public JSONRPC {
GDCLASS(GDScriptLanguageProtocol, JSONRPC)
private:
struct LSPeer : RefCounted {
Ref<StreamPeerTCP> connection;
uint8_t req_buf[LSP_MAX_BUFFER_SIZE];
int req_pos = 0;
bool has_header = false;
bool has_content = false;
int content_length = 0;
Vector<CharString> res_queue;
int res_sent = 0;
Error handle_data();
Error send_data();
};
enum LSPErrorCode {
RequestCancelled = -32800,
ContentModified = -32801,
};
static GDScriptLanguageProtocol *singleton;
HashMap<int, Ref<LSPeer>> clients;
Ref<TCPServer> server;
int latest_client_id = 0;
int next_client_id = 0;
int next_server_id = 0;
Ref<GDScriptTextDocument> text_document;
Ref<GDScriptWorkspace> workspace;
Error on_client_connected();
void on_client_disconnected(const int &p_client_id);
String process_message(const String &p_text);
String format_output(const String &p_text);
bool _initialized = false;
protected:
static void _bind_methods();
Dictionary initialize(const Dictionary &p_params);
void initialized(const Variant &p_params);
public:
_FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
_FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
_FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
_FORCE_INLINE_ bool is_initialized() const { return _initialized; }
void poll(int p_limit_usec);
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);
void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1);
bool is_smart_resolve_enabled() const;
bool is_goto_native_symbols_enabled() const;
GDScriptLanguageProtocol();
};
#endif // GDSCRIPT_LANGUAGE_PROTOCOL_H

View file

@ -0,0 +1,124 @@
/**************************************************************************/
/* gdscript_language_server.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_language_server.h"
#include "core/io/file_access.h"
#include "core/os/os.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
int GDScriptLanguageServer::port_override = -1;
GDScriptLanguageServer::GDScriptLanguageServer() {
_EDITOR_DEF("network/language_server/remote_host", host);
_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);
_EDITOR_DEF("network/language_server/use_thread", use_thread);
_EDITOR_DEF("network/language_server/poll_limit_usec", poll_limit_usec);
}
void GDScriptLanguageServer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
start();
} break;
case NOTIFICATION_EXIT_TREE: {
stop();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (started && !use_thread) {
protocol.poll(poll_limit_usec);
}
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("network/language_server")) {
break;
}
String remote_host = String(_EDITOR_GET("network/language_server/remote_host"));
int remote_port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port");
bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
int remote_poll_limit = (int)_EDITOR_GET("network/language_server/poll_limit_usec");
if (remote_host != host || remote_port != port || remote_use_thread != use_thread || remote_poll_limit != poll_limit_usec) {
stop();
start();
}
} break;
}
}
void GDScriptLanguageServer::thread_main(void *p_userdata) {
set_current_thread_safe_for_nodes(true);
GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
while (self->thread_running) {
// Poll 20 times per second
self->protocol.poll(self->poll_limit_usec);
OS::get_singleton()->delay_usec(50000);
}
}
void GDScriptLanguageServer::start() {
host = String(_EDITOR_GET("network/language_server/remote_host"));
port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port");
use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
poll_limit_usec = (int)_EDITOR_GET("network/language_server/poll_limit_usec");
if (protocol.start(port, IPAddress(host)) == OK) {
EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
thread.start(GDScriptLanguageServer::thread_main, this);
}
set_process_internal(!use_thread);
started = true;
}
}
void GDScriptLanguageServer::stop() {
if (use_thread) {
ERR_FAIL_COND(!thread.is_started());
thread_running = false;
thread.wait_to_finish();
}
protocol.stop();
started = false;
EditorNode::get_log()->add_message("--- GDScript language server stopped ---", EditorLog::MSG_TYPE_EDITOR);
}
void register_lsp_types() {
GDREGISTER_CLASS(GDScriptLanguageProtocol);
GDREGISTER_CLASS(GDScriptTextDocument);
GDREGISTER_CLASS(GDScriptWorkspace);
}

View file

@ -0,0 +1,65 @@
/**************************************************************************/
/* gdscript_language_server.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_LANGUAGE_SERVER_H
#define GDSCRIPT_LANGUAGE_SERVER_H
#include "../gdscript_parser.h"
#include "gdscript_language_protocol.h"
#include "editor/plugins/editor_plugin.h"
class GDScriptLanguageServer : public EditorPlugin {
GDCLASS(GDScriptLanguageServer, EditorPlugin);
GDScriptLanguageProtocol protocol;
Thread thread;
bool thread_running = false;
bool started = false;
bool use_thread = false;
String host = "127.0.0.1";
int port = 6005;
int poll_limit_usec = 100000;
static void thread_main(void *p_userdata);
private:
void _notification(int p_what);
public:
static int port_override;
GDScriptLanguageServer();
void start();
void stop();
};
void register_lsp_types();
#endif // GDSCRIPT_LANGUAGE_SERVER_H

View file

@ -0,0 +1,526 @@
/**************************************************************************/
/* gdscript_text_document.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_text_document.h"
#include "../gdscript.h"
#include "gdscript_extend_parser.h"
#include "gdscript_language_protocol.h"
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/plugins/script_text_editor.h"
#include "servers/display_server.h"
void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose);
ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
ClassDB::bind_method(D_METHOD("willSaveWaitUntil"), &GDScriptTextDocument::willSaveWaitUntil);
ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave);
ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);
ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve);
ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename);
ClassDB::bind_method(D_METHOD("prepareRename"), &GDScriptTextDocument::prepareRename);
ClassDB::bind_method(D_METHOD("references"), &GDScriptTextDocument::references);
ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);
ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
ClassDB::bind_method(D_METHOD("colorPresentation"), &GDScriptTextDocument::colorPresentation);
ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
ClassDB::bind_method(D_METHOD("declaration"), &GDScriptTextDocument::declaration);
ClassDB::bind_method(D_METHOD("signatureHelp"), &GDScriptTextDocument::signatureHelp);
ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
}
void GDScriptTextDocument::didOpen(const Variant &p_param) {
lsp::TextDocumentItem doc = load_document_item(p_param);
sync_script_content(doc.uri, doc.text);
}
void GDScriptTextDocument::didClose(const Variant &p_param) {
// Left empty on purpose. Godot does nothing special on closing a document,
// but it satisfies LSP clients that require didClose be implemented.
}
void GDScriptTextDocument::didChange(const Variant &p_param) {
lsp::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
Array contentChanges = dict["contentChanges"];
for (int i = 0; i < contentChanges.size(); ++i) {
lsp::TextDocumentContentChangeEvent evt;
evt.load(contentChanges[i]);
doc.text = evt.text;
}
sync_script_content(doc.uri, doc.text);
}
void GDScriptTextDocument::willSaveWaitUntil(const Variant &p_param) {
lsp::TextDocumentItem doc = load_document_item(p_param);
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
Ref<Script> scr = ResourceLoader::load(path);
if (scr.is_valid()) {
ScriptEditor::get_singleton()->clear_docs_from_script(scr);
}
}
void GDScriptTextDocument::didSave(const Variant &p_param) {
lsp::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
String text = dict["text"];
sync_script_content(doc.uri, text);
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);
Ref<GDScript> scr = ResourceLoader::load(path);
if (scr.is_valid() && (scr->load_source_code(path) == OK)) {
if (scr->is_tool()) {
scr->get_language()->reload_tool_script(scr, true);
} else {
scr->reload(true);
}
scr->update_exports();
ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(scr);
ScriptEditor::get_singleton()->trigger_live_script_reload(scr->get_path());
}
}
lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
lsp::TextDocumentItem doc;
Dictionary params = p_param;
doc.load(params["textDocument"]);
return doc;
}
void GDScriptTextDocument::notify_client_show_symbol(const lsp::DocumentSymbol *symbol) {
ERR_FAIL_NULL(symbol);
GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true));
}
void GDScriptTextDocument::initialize() {
if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
for (const KeyValue<StringName, ClassMembers> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members) {
const ClassMembers &members = E.value;
for (const KeyValue<String, const lsp::DocumentSymbol *> &F : members) {
const lsp::DocumentSymbol *symbol = members.get(F.key);
lsp::CompletionItem item = symbol->make_completion_item();
item.data = JOIN_SYMBOLS(String(E.key), F.key);
native_member_completions.push_back(item.to_json());
}
}
}
}
Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) {
Variant ret;
lsp::NativeSymbolInspectParams params;
params.load(p_params);
if (const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_native_symbol(params)) {
ret = symbol->to_json(true);
notify_client_show_symbol(symbol);
}
return ret;
}
Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
Dictionary params = p_params["textDocument"];
String uri = params["uri"];
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri);
Array arr;
if (HashMap<String, ExtendGDScriptParser *>::ConstIterator parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) {
lsp::DocumentSymbol symbol = parser->value->get_symbols();
arr.push_back(symbol.to_json(true));
}
return arr;
}
Array GDScriptTextDocument::completion(const Dictionary &p_params) {
Array arr;
lsp::CompletionParams params;
params.load(p_params);
Dictionary request_data = params.to_json();
List<ScriptLanguage::CodeCompletionOption> options;
GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options);
if (!options.is_empty()) {
int i = 0;
arr.resize(options.size());
for (const ScriptLanguage::CodeCompletionOption &option : options) {
lsp::CompletionItem item;
item.label = option.display;
item.data = request_data;
item.insertText = option.insert_text;
switch (option.kind) {
case ScriptLanguage::CODE_COMPLETION_KIND_ENUM:
item.kind = lsp::CompletionItemKind::Enum;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_CLASS:
item.kind = lsp::CompletionItemKind::Class;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_MEMBER:
item.kind = lsp::CompletionItemKind::Property;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION:
item.kind = lsp::CompletionItemKind::Method;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL:
item.kind = lsp::CompletionItemKind::Event;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT:
item.kind = lsp::CompletionItemKind::Constant;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE:
item.kind = lsp::CompletionItemKind::Variable;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH:
item.kind = lsp::CompletionItemKind::File;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH:
item.kind = lsp::CompletionItemKind::Snippet;
break;
case ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT:
item.kind = lsp::CompletionItemKind::Text;
break;
default: {
}
}
arr[i] = item.to_json();
i++;
}
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
arr = native_member_completions.duplicate();
for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) {
ExtendGDScriptParser *scr = E.value;
const Array &items = scr->get_member_completions();
const int start_size = arr.size();
arr.resize(start_size + items.size());
for (int i = start_size; i < arr.size(); i++) {
arr[i] = items[i - start_size];
}
}
}
return arr;
}
Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
String new_name = p_params["newName"];
return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name);
}
Variant GDScriptTextDocument::prepareRename(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
lsp::DocumentSymbol symbol;
lsp::Range range;
if (GDScriptLanguageProtocol::get_singleton()->get_workspace()->can_rename(params, symbol, range)) {
return Variant(range.to_json());
}
// `null` -> rename not valid at current location.
return Variant();
}
Array GDScriptTextDocument::references(const Dictionary &p_params) {
Array res;
lsp::ReferenceParams params;
params.load(p_params);
const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
if (symbol) {
Vector<lsp::Location> usages = GDScriptLanguageProtocol::get_singleton()->get_workspace()->find_all_usages(*symbol);
res.resize(usages.size());
int declaration_adjustment = 0;
for (int i = 0; i < usages.size(); i++) {
lsp::Location usage = usages[i];
if (!params.context.includeDeclaration && usage.range == symbol->range) {
declaration_adjustment++;
continue;
}
res[i - declaration_adjustment] = usages[i].to_json();
}
if (declaration_adjustment > 0) {
res.resize(res.size() - declaration_adjustment);
}
}
return res;
}
Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
lsp::CompletionItem item;
item.load(p_params);
lsp::CompletionParams params;
Variant data = p_params["data"];
const lsp::DocumentSymbol *symbol = nullptr;
if (data.get_type() == Variant::DICTIONARY) {
params.load(p_params["data"]);
symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function);
} else if (data.get_type() == Variant::STRING) {
String query = data;
Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false);
if (param_symbols.size() >= 2) {
StringName class_name = param_symbols[0];
const String &member_name = param_symbols[param_symbols.size() - 1];
String inner_class_name;
if (param_symbols.size() >= 3) {
inner_class_name = param_symbols[1];
}
if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members.getptr(class_name)) {
if (const lsp::DocumentSymbol *const *member = members->getptr(member_name)) {
symbol = *member;
}
}
if (!symbol) {
if (HashMap<String, ExtendGDScriptParser *>::ConstIterator E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(class_name)) {
symbol = E->value->get_member_symbol(member_name, inner_class_name);
}
}
}
}
if (symbol) {
item.documentation = symbol->render();
}
if (item.kind == lsp::CompletionItemKind::Event) {
if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) {
const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\"";
item.insertText = item.label.quote(quote_style);
}
}
if (item.kind == lsp::CompletionItemKind::Method) {
bool is_trigger_character = params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter;
bool is_quote_character = params.context.triggerCharacter == "\"" || params.context.triggerCharacter == "'";
if (is_trigger_character && is_quote_character && item.insertText.is_quoted()) {
item.insertText = item.insertText.unquote();
}
}
return item.to_json(true);
}
Array GDScriptTextDocument::foldingRange(const Dictionary &p_params) {
Array arr;
return arr;
}
Array GDScriptTextDocument::codeLens(const Dictionary &p_params) {
Array arr;
return arr;
}
Array GDScriptTextDocument::documentLink(const Dictionary &p_params) {
Array ret;
lsp::DocumentLinkParams params;
params.load(p_params);
List<lsp::DocumentLink> links;
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links);
for (const lsp::DocumentLink &E : links) {
ret.push_back(E.to_json());
}
return ret;
}
Array GDScriptTextDocument::colorPresentation(const Dictionary &p_params) {
Array arr;
return arr;
}
Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
if (symbol) {
lsp::Hover hover;
hover.contents = symbol->render();
hover.range.start = params.position;
hover.range.end = params.position;
return hover.to_json();
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
Dictionary ret;
Array contents;
List<const lsp::DocumentSymbol *> list;
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list);
for (const lsp::DocumentSymbol *&E : list) {
if (const lsp::DocumentSymbol *s = E) {
contents.push_back(s->render().value);
}
}
ret["contents"] = contents;
return ret;
}
return Variant();
}
Array GDScriptTextDocument::definition(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
List<const lsp::DocumentSymbol *> symbols;
Array arr = find_symbols(params, symbols);
return arr;
}
Variant GDScriptTextDocument::declaration(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
List<const lsp::DocumentSymbol *> symbols;
Array arr = find_symbols(params, symbols);
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;
switch (symbol->kind) {
case lsp::SymbolKind::Class:
id = "class_name:" + symbol->name;
break;
case lsp::SymbolKind::Constant:
id = "class_constant:" + symbol->native_class + ":" + symbol->name;
break;
case lsp::SymbolKind::Property:
case lsp::SymbolKind::Variable:
id = "class_property:" + symbol->native_class + ":" + symbol->name;
break;
case lsp::SymbolKind::Enum:
id = "class_enum:" + symbol->native_class + ":" + symbol->name;
break;
case lsp::SymbolKind::Method:
case lsp::SymbolKind::Function:
id = "class_method:" + symbol->native_class + ":" + symbol->name;
break;
default:
id = "class_global:" + symbol->native_class + ":" + symbol->name;
break;
}
callable_mp(this, &GDScriptTextDocument::show_native_symbol_in_editor).call_deferred(id);
} else {
notify_client_show_symbol(symbol);
}
}
return arr;
}
Variant GDScriptTextDocument::signatureHelp(const Dictionary &p_params) {
Variant ret;
lsp::TextDocumentPositionParams params;
params.load(p_params);
lsp::SignatureHelp s;
if (OK == GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_signature(params, s)) {
ret = s.to_json();
}
return ret;
}
GDScriptTextDocument::GDScriptTextDocument() {
file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
}
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) {
callable_mp(ScriptEditor::get_singleton(), &ScriptEditor::goto_help).call_deferred(p_symbol_id);
DisplayServer::get_singleton()->window_move_to_foreground();
}
Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &p_location, List<const lsp::DocumentSymbol *> &r_list) {
Array arr;
const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(p_location);
if (symbol) {
lsp::Location location;
location.uri = symbol->uri;
location.range = symbol->selectionRange;
const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri);
if (file_checker->file_exists(path)) {
arr.push_back(location.to_json());
}
r_list.push_back(symbol);
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
List<const lsp::DocumentSymbol *> list;
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(p_location, list);
for (const lsp::DocumentSymbol *&E : list) {
if (const lsp::DocumentSymbol *s = E) {
if (!s->uri.is_empty()) {
lsp::Location location;
location.uri = s->uri;
location.range = s->selectionRange;
arr.push_back(location.to_json());
r_list.push_back(s);
}
}
}
}
return arr;
}

View file

@ -0,0 +1,84 @@
/**************************************************************************/
/* gdscript_text_document.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_TEXT_DOCUMENT_H
#define GDSCRIPT_TEXT_DOCUMENT_H
#include "godot_lsp.h"
#include "core/io/file_access.h"
#include "core/object/ref_counted.h"
class GDScriptTextDocument : public RefCounted {
GDCLASS(GDScriptTextDocument, RefCounted)
protected:
static void _bind_methods();
Ref<FileAccess> file_checker;
void didOpen(const Variant &p_param);
void didClose(const Variant &p_param);
void didChange(const Variant &p_param);
void willSaveWaitUntil(const Variant &p_param);
void didSave(const Variant &p_param);
void sync_script_content(const String &p_path, const String &p_content);
void show_native_symbol_in_editor(const String &p_symbol_id);
Array native_member_completions;
private:
Array find_symbols(const lsp::TextDocumentPositionParams &p_location, List<const lsp::DocumentSymbol *> &r_list);
lsp::TextDocumentItem load_document_item(const Variant &p_param);
void notify_client_show_symbol(const lsp::DocumentSymbol *symbol);
public:
Variant nativeSymbol(const Dictionary &p_params);
Array documentSymbol(const Dictionary &p_params);
Array completion(const Dictionary &p_params);
Dictionary resolve(const Dictionary &p_params);
Dictionary rename(const Dictionary &p_params);
Variant prepareRename(const Dictionary &p_params);
Array references(const Dictionary &p_params);
Array foldingRange(const Dictionary &p_params);
Array codeLens(const Dictionary &p_params);
Array documentLink(const Dictionary &p_params);
Array colorPresentation(const Dictionary &p_params);
Variant hover(const Dictionary &p_params);
Array definition(const Dictionary &p_params);
Variant declaration(const Dictionary &p_params);
Variant signatureHelp(const Dictionary &p_params);
void initialize();
GDScriptTextDocument();
};
#endif // GDSCRIPT_TEXT_DOCUMENT_H

View file

@ -0,0 +1,866 @@
/**************************************************************************/
/* gdscript_workspace.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_workspace.h"
#include "../gdscript.h"
#include "../gdscript_parser.h"
#include "gdscript_language_protocol.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"
#include "editor/editor_settings.h"
#include "scene/resources/packed_scene.h"
void GDScriptWorkspace::_bind_methods() {
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files);
ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);
ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script);
ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path);
ClassDB::bind_method(D_METHOD("get_file_uri", "path"), &GDScriptWorkspace::get_file_uri);
ClassDB::bind_method(D_METHOD("publish_diagnostics", "path"), &GDScriptWorkspace::publish_diagnostics);
ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
}
void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) {
Ref<Script> scr = obj->get_script();
if (scr->get_language()->get_name() != "GDScript") {
return;
}
String function_signature = "func " + function;
String source = scr->get_source_code();
if (source.contains(function_signature)) {
return;
}
int first_class = source.find("\nclass ");
int start_line = 0;
if (first_class != -1) {
start_line = source.substr(0, first_class).split("\n").size();
} else {
start_line = source.split("\n").size();
}
String function_body = "\n\n" + function_signature + "(";
for (int i = 0; i < args.size(); ++i) {
function_body += args[i];
if (i < args.size() - 1) {
function_body += ", ";
}
}
function_body += ")";
if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) {
function_body += " -> void";
}
function_body += ":\n\tpass # Replace with function body.\n";
lsp::TextEdit text_edit;
if (first_class != -1) {
function_body += "\n\n";
}
text_edit.range.end.line = text_edit.range.start.line = start_line;
text_edit.newText = function_body;
String uri = get_file_uri(scr->get_path());
lsp::ApplyWorkspaceEditParams params;
params.edit.add_edit(uri, text_edit);
GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json());
}
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) {
HashMap<String, ExtendGDScriptParser *>::Iterator parser = parse_results.find(p_path);
HashMap<String, ExtendGDScriptParser *>::Iterator scr = scripts.find(p_path);
if (parser && scr) {
if (scr->value && scr->value == parser->value) {
memdelete(scr->value);
} else {
memdelete(scr->value);
memdelete(parser->value);
}
parse_results.erase(p_path);
scripts.erase(p_path);
} else if (parser) {
memdelete(parser->value);
parse_results.erase(p_path);
} else if (scr) {
memdelete(scr->value);
scripts.erase(p_path);
}
}
const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const {
StringName class_name = p_class;
StringName empty;
while (class_name != empty) {
if (HashMap<StringName, lsp::DocumentSymbol>::ConstIterator E = native_symbols.find(class_name)) {
const lsp::DocumentSymbol &class_symbol = E->value;
if (p_member.is_empty()) {
return &class_symbol;
} else {
for (int i = 0; i < class_symbol.children.size(); i++) {
const lsp::DocumentSymbol &symbol = class_symbol.children[i];
if (symbol.name == p_member) {
return &symbol;
}
}
}
}
class_name = ClassDB::get_parent_class(class_name);
}
return nullptr;
}
const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const {
HashMap<String, ExtendGDScriptParser *>::ConstIterator S = scripts.find(p_path);
if (S) {
return &(S->value->get_symbols());
}
return nullptr;
}
const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier) {
for (int i = 0; i < p_parent->children.size(); ++i) {
const lsp::DocumentSymbol *parameter_symbol = &p_parent->children[i];
if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) {
return parameter_symbol;
}
}
return nullptr;
}
const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position) {
// Go down and pick closest `DocumentSymbol` with `p_symbol_identifier`.
const lsp::DocumentSymbol *current = &p_parser->get_symbols();
const lsp::DocumentSymbol *best_match = nullptr;
while (current) {
if (current->name == p_symbol_identifier) {
if (current->selectionRange.contains(p_position)) {
// Exact match: pos is ON symbol decl identifier.
return current;
}
best_match = current;
}
const lsp::DocumentSymbol *parent = current;
current = nullptr;
for (const lsp::DocumentSymbol &child : parent->children) {
if (child.range.contains(p_position)) {
current = &child;
break;
}
}
}
return best_match;
}
void GDScriptWorkspace::reload_all_workspace_scripts() {
List<String> paths;
list_script_files("res://", paths);
for (const String &path : paths) {
Error err;
String content = FileAccess::get_file_as_string(path, &err);
ERR_CONTINUE(err != OK);
err = parse_script(path, content);
if (err != OK) {
HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(path);
String err_msg = "Failed parse script " + path;
if (S) {
err_msg += "\n" + S->value->get_errors().front()->get().message;
}
ERR_CONTINUE_MSG(err != OK, err_msg);
}
}
}
void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) {
Error err;
Ref<DirAccess> dir = DirAccess::open(p_root_dir, &err);
if (OK != err) {
return;
}
// Ignore scripts in directories with a .gdignore file.
if (dir->file_exists(".gdignore")) {
return;
}
dir->list_dir_begin();
String file_name = dir->get_next();
while (file_name.length()) {
if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./") {
list_script_files(p_root_dir.path_join(file_name), r_files);
} else if (file_name.ends_with(".gd")) {
String script_file = p_root_dir.path_join(file_name);
r_files.push_back(script_file);
}
file_name = dir->get_next();
}
}
ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) {
HashMap<String, ExtendGDScriptParser *>::Iterator S = scripts.find(p_path);
if (!S) {
parse_local_script(p_path);
S = scripts.find(p_path);
}
if (S) {
return S->value;
}
return nullptr;
}
ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) {
HashMap<String, ExtendGDScriptParser *>::Iterator S = parse_results.find(p_path);
if (!S) {
parse_local_script(p_path);
S = parse_results.find(p_path);
}
if (S) {
return S->value;
}
return nullptr;
}
Error GDScriptWorkspace::initialize() {
if (initialized) {
return OK;
}
DocTools *doc = EditorHelp::get_doc_data();
for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {
const DocData::ClassDoc &class_data = E.value;
lsp::DocumentSymbol class_symbol;
String class_name = E.key;
class_symbol.name = class_name;
class_symbol.native_class = class_name;
class_symbol.kind = lsp::SymbolKind::Class;
class_symbol.detail = String("<Native> class ") + class_name;
if (!class_data.inherits.is_empty()) {
class_symbol.detail += " extends " + class_data.inherits;
}
class_symbol.documentation = class_data.brief_description + "\n" + class_data.description;
for (int i = 0; i < class_data.constants.size(); i++) {
const DocData::ConstantDoc &const_data = class_data.constants[i];
lsp::DocumentSymbol symbol;
symbol.name = const_data.name;
symbol.native_class = class_name;
symbol.kind = lsp::SymbolKind::Constant;
symbol.detail = "const " + class_name + "." + const_data.name;
if (const_data.enumeration.length()) {
symbol.detail += ": " + const_data.enumeration;
}
symbol.detail += " = " + const_data.value;
symbol.documentation = const_data.description;
class_symbol.children.push_back(symbol);
}
for (int i = 0; i < class_data.properties.size(); i++) {
const DocData::PropertyDoc &data = class_data.properties[i];
lsp::DocumentSymbol symbol;
symbol.name = data.name;
symbol.native_class = class_name;
symbol.kind = lsp::SymbolKind::Property;
symbol.detail = "var " + class_name + "." + data.name;
if (data.enumeration.length()) {
symbol.detail += ": " + data.enumeration;
} else {
symbol.detail += ": " + data.type;
}
symbol.documentation = data.description;
class_symbol.children.push_back(symbol);
}
for (int i = 0; i < class_data.theme_properties.size(); i++) {
const DocData::ThemeItemDoc &data = class_data.theme_properties[i];
lsp::DocumentSymbol symbol;
symbol.name = data.name;
symbol.native_class = class_name;
symbol.kind = lsp::SymbolKind::Property;
symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type;
symbol.documentation = data.description;
class_symbol.children.push_back(symbol);
}
Vector<DocData::MethodDoc> methods_signals;
methods_signals.append_array(class_data.constructors);
methods_signals.append_array(class_data.methods);
methods_signals.append_array(class_data.operators);
const int signal_start_idx = methods_signals.size();
methods_signals.append_array(class_data.signals);
for (int i = 0; i < methods_signals.size(); i++) {
const DocData::MethodDoc &data = methods_signals[i];
lsp::DocumentSymbol symbol;
symbol.name = data.name;
symbol.native_class = class_name;
symbol.kind = i >= signal_start_idx ? lsp::SymbolKind::Event : lsp::SymbolKind::Method;
String params = "";
bool arg_default_value_started = false;
for (int j = 0; j < data.arguments.size(); j++) {
const DocData::ArgumentDoc &arg = data.arguments[j];
lsp::DocumentSymbol symbol_arg;
symbol_arg.name = arg.name;
symbol_arg.kind = lsp::SymbolKind::Variable;
symbol_arg.detail = arg.type;
if (!arg_default_value_started && !arg.default_value.is_empty()) {
arg_default_value_started = true;
}
String arg_str = arg.name + ": " + arg.type;
if (arg_default_value_started) {
arg_str += " = " + arg.default_value;
}
if (j < data.arguments.size() - 1) {
arg_str += ", ";
}
params += arg_str;
symbol.children.push_back(symbol_arg);
}
if (data.qualifiers.contains("vararg")) {
params += params.is_empty() ? "..." : ", ...";
}
String return_type = data.return_type;
if (return_type.is_empty()) {
return_type = "void";
}
symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type;
symbol.documentation = data.description;
class_symbol.children.push_back(symbol);
}
native_symbols.insert(class_name, class_symbol);
}
reload_all_workspace_scripts();
if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
for (const KeyValue<StringName, lsp::DocumentSymbol> &E : native_symbols) {
ClassMembers members;
const lsp::DocumentSymbol &class_symbol = E.value;
for (int i = 0; i < class_symbol.children.size(); i++) {
const lsp::DocumentSymbol &symbol = class_symbol.children[i];
members.insert(symbol.name, &symbol);
}
native_members.insert(E.key, members);
}
// Cache member completions.
for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) {
S.value->get_member_completions();
}
}
EditorNode *editor_node = EditorNode::get_singleton();
editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal));
return OK;
}
Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) {
ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser);
Error err = parser->parse(p_content, p_path);
HashMap<String, ExtendGDScriptParser *>::Iterator last_parser = parse_results.find(p_path);
HashMap<String, ExtendGDScriptParser *>::Iterator last_script = scripts.find(p_path);
if (err == OK) {
remove_cache_parser(p_path);
parse_results[p_path] = parser;
scripts[p_path] = parser;
} else {
if (last_parser && last_script && last_parser->value != last_script->value) {
memdelete(last_parser->value);
}
parse_results[p_path] = parser;
}
publish_diagnostics(p_path);
return err;
}
static bool is_valid_rename_target(const lsp::DocumentSymbol *p_symbol) {
// Must be valid symbol.
if (!p_symbol) {
return false;
}
// Cannot rename builtin.
if (!p_symbol->native_class.is_empty()) {
return false;
}
// Source must be available.
if (p_symbol->script_path.is_empty()) {
return false;
}
return true;
}
Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) {
lsp::WorkspaceEdit edit;
const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);
if (is_valid_rename_target(reference_symbol)) {
Vector<lsp::Location> usages = find_all_usages(*reference_symbol);
for (int i = 0; i < usages.size(); ++i) {
lsp::Location loc = usages[i];
edit.add_change(loc.uri, loc.range.start.line, loc.range.start.character, loc.range.end.character, new_name);
}
}
return edit.to_json();
}
bool GDScriptWorkspace::can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range) {
const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);
if (!is_valid_rename_target(reference_symbol)) {
return false;
}
String path = get_file_path(p_doc_pos.textDocument.uri);
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
parser->get_identifier_under_position(p_doc_pos.position, r_range);
r_symbol = *reference_symbol;
return true;
}
return false;
}
Vector<lsp::Location> GDScriptWorkspace::find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path) {
Vector<lsp::Location> usages;
String identifier = p_symbol.name;
if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) {
const PackedStringArray &content = parser->get_lines();
for (int i = 0; i < content.size(); ++i) {
String line = content[i];
int character = line.find(identifier);
while (character > -1) {
lsp::TextDocumentPositionParams params;
lsp::TextDocumentIdentifier text_doc;
text_doc.uri = get_file_uri(p_file_path);
params.textDocument = text_doc;
params.position.line = i;
params.position.character = character;
const lsp::DocumentSymbol *other_symbol = resolve_symbol(params);
if (other_symbol == &p_symbol) {
lsp::Location loc;
loc.uri = text_doc.uri;
loc.range.start = params.position;
loc.range.end.line = params.position.line;
loc.range.end.character = params.position.character + identifier.length();
usages.append(loc);
}
character = line.find(identifier, character + 1);
}
}
}
return usages;
}
Vector<lsp::Location> GDScriptWorkspace::find_all_usages(const lsp::DocumentSymbol &p_symbol) {
if (p_symbol.local) {
// Only search in current document.
return find_usages_in_file(p_symbol, p_symbol.script_path);
}
// Search in all documents.
List<String> paths;
list_script_files("res://", paths);
Vector<lsp::Location> usages;
for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) {
usages.append_array(find_usages_in_file(p_symbol, PE->get()));
}
return usages;
}
Error GDScriptWorkspace::parse_local_script(const String &p_path) {
Error err;
String content = FileAccess::get_file_as_string(p_path, &err);
if (err == OK) {
err = parse_script(p_path, content);
}
return err;
}
String GDScriptWorkspace::get_file_path(const String &p_uri) const {
String path = p_uri.uri_decode();
String base_uri = root_uri.uri_decode();
path = path.replacen(base_uri + "/", "res://");
return path;
}
String GDScriptWorkspace::get_file_uri(const String &p_path) const {
String uri = p_path;
uri = uri.replace("res://", root_uri + "/");
return uri;
}
void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
Dictionary params;
Array errors;
HashMap<String, ExtendGDScriptParser *>::ConstIterator ele = parse_results.find(p_path);
if (ele) {
const Vector<lsp::Diagnostic> &list = ele->value->get_diagnostics();
errors.resize(list.size());
for (int i = 0; i < list.size(); ++i) {
errors[i] = list[i].to_json();
}
}
params["diagnostics"] = errors;
params["uri"] = get_file_uri(p_path);
GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
}
void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) {
if (!efsd) {
return;
}
for (int i = 0; i < efsd->get_subdir_count(); i++) {
_get_owners(efsd->get_subdir(i), p_path, owners);
}
for (int i = 0; i < efsd->get_file_count(); i++) {
Vector<String> deps = efsd->get_file_deps(i);
bool found = false;
for (int j = 0; j < deps.size(); j++) {
if (deps[j] == p_path) {
found = true;
break;
}
}
if (!found) {
continue;
}
owners.push_back(efsd->get_file_path(i));
}
}
Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {
Node *owner_scene_node = nullptr;
List<String> owners;
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);
for (const String &owner : owners) {
NodePath owner_path = owner;
Ref<Resource> owner_res = ResourceLoader::load(owner_path);
if (Object::cast_to<PackedScene>(owner_res.ptr())) {
Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res));
owner_scene_node = owner_packed_scene->instantiate();
break;
}
}
return owner_scene_node;
}
void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) {
String path = get_file_path(p_params.textDocument.uri);
String call_hint;
bool forced = false;
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
Node *owner_scene_node = _get_owner_scene_node(path);
Array stack;
Node *current = nullptr;
if (owner_scene_node != nullptr) {
stack.push_back(owner_scene_node);
while (!stack.is_empty()) {
current = Object::cast_to<Node>(stack.pop_back());
Ref<GDScript> scr = current->get_script();
if (scr.is_valid() && GDScript::is_canonically_equal_paths(scr->get_path(), path)) {
break;
}
for (int i = 0; i < current->get_child_count(); ++i) {
stack.push_back(current->get_child(i));
}
}
Ref<GDScript> scr = current->get_script();
if (!scr.is_valid() || !GDScript::is_canonically_equal_paths(scr->get_path(), path)) {
current = owner_scene_node;
}
}
String code = parser->get_text_for_completion(p_params.position);
GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);
if (owner_scene_node) {
memdelete(owner_scene_node);
}
}
}
const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) {
const lsp::DocumentSymbol *symbol = nullptr;
String path = get_file_path(p_doc_pos.textDocument.uri);
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
String symbol_identifier = p_symbol_name;
Vector<String> identifier_parts = symbol_identifier.split("(");
if (identifier_parts.size()) {
symbol_identifier = identifier_parts[0];
}
lsp::Position pos = p_doc_pos.position;
if (symbol_identifier.is_empty()) {
lsp::Range range;
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
pos.character = range.end.character;
}
if (!symbol_identifier.is_empty()) {
if (ScriptServer::is_global_class(symbol_identifier)) {
String class_path = ScriptServer::get_global_class_path(symbol_identifier);
symbol = get_script_symbol(class_path);
} else {
ScriptLanguage::LookupResult ret;
if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" ", "").replace("\t", "").contains("new(")) {
symbol_identifier = "_init";
}
if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) {
if (ret.type == ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION) {
String target_script_path = path;
if (!ret.script.is_null()) {
target_script_path = ret.script->get_path();
} else if (!ret.class_path.is_empty()) {
target_script_path = ret.class_path;
}
if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier);
if (symbol) {
switch (symbol->kind) {
case lsp::SymbolKind::Function: {
if (symbol->name != symbol_identifier) {
symbol = get_parameter_symbol(symbol, symbol_identifier);
}
} break;
}
}
}
} else {
String member = ret.class_member;
if (member.is_empty() && symbol_identifier != ret.class_name) {
member = symbol_identifier;
}
symbol = get_native_symbol(ret.class_name, member);
}
} else {
symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position);
if (!symbol) {
symbol = parser->get_member_symbol(symbol_identifier);
}
}
}
}
}
return symbol;
}
void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list) {
String path = get_file_path(p_doc_pos.textDocument.uri);
if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
String symbol_identifier;
lsp::Range range;
symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range);
for (const KeyValue<StringName, ClassMembers> &E : native_members) {
const ClassMembers &members = native_members.get(E.key);
if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
r_list.push_back(*symbol);
}
}
for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
const ExtendGDScriptParser *scr = E.value;
const ClassMembers &members = scr->get_members();
if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
r_list.push_back(*symbol);
}
for (const KeyValue<String, ClassMembers> &F : scr->get_inner_classes()) {
const ClassMembers *inner_class = &F.value;
if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) {
r_list.push_back(*symbol);
}
}
}
}
}
const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) {
if (HashMap<StringName, lsp::DocumentSymbol>::Iterator E = native_symbols.find(p_params.native_class)) {
const lsp::DocumentSymbol &symbol = E->value;
if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) {
return &symbol;
}
for (int i = 0; i < symbol.children.size(); ++i) {
if (symbol.children[i].name == p_params.symbol_name) {
return &(symbol.children[i]);
}
}
}
return nullptr;
}
void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) {
if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) {
const List<lsp::DocumentLink> &links = parser->get_document_links();
for (const lsp::DocumentLink &E : links) {
r_list.push_back(E);
}
}
}
Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
Dictionary api;
if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) {
api = parser->generate_api();
}
return api;
}
Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature) {
if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) {
lsp::TextDocumentPositionParams text_pos;
text_pos.textDocument = p_doc_pos.textDocument;
if (parser->get_left_function_call(p_doc_pos.position, text_pos.position, r_signature.activeParameter) == OK) {
List<const lsp::DocumentSymbol *> symbols;
if (const lsp::DocumentSymbol *symbol = resolve_symbol(text_pos)) {
symbols.push_back(symbol);
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols);
}
for (const lsp::DocumentSymbol *const &symbol : symbols) {
if (symbol->kind == lsp::SymbolKind::Method || symbol->kind == lsp::SymbolKind::Function) {
lsp::SignatureInformation signature_info;
signature_info.label = symbol->detail;
signature_info.documentation = symbol->render();
for (int i = 0; i < symbol->children.size(); i++) {
const lsp::DocumentSymbol &arg = symbol->children[i];
lsp::ParameterInformation arg_info;
arg_info.label = arg.name;
signature_info.parameters.push_back(arg_info);
}
r_signature.signatures.push_back(signature_info);
break;
}
}
if (r_signature.signatures.size()) {
return OK;
}
}
}
return ERR_METHOD_NOT_FOUND;
}
GDScriptWorkspace::GDScriptWorkspace() {
ProjectSettings::get_singleton()->get_resource_path();
}
GDScriptWorkspace::~GDScriptWorkspace() {
HashSet<String> cached_parsers;
for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) {
cached_parsers.insert(E.key);
}
for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) {
cached_parsers.insert(E.key);
}
for (const String &E : cached_parsers) {
remove_cache_parser(E);
}
}

View file

@ -0,0 +1,104 @@
/**************************************************************************/
/* gdscript_workspace.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_WORKSPACE_H
#define GDSCRIPT_WORKSPACE_H
#include "../gdscript_parser.h"
#include "gdscript_extend_parser.h"
#include "godot_lsp.h"
#include "core/variant/variant.h"
#include "editor/editor_file_system.h"
class GDScriptWorkspace : public RefCounted {
GDCLASS(GDScriptWorkspace, RefCounted);
private:
void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners);
Node *_get_owner_scene_node(String p_path);
protected:
static void _bind_methods();
void remove_cache_parser(const String &p_path);
bool initialized = false;
HashMap<StringName, lsp::DocumentSymbol> native_symbols;
const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const;
const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const;
const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier);
const lsp::DocumentSymbol *get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position);
void reload_all_workspace_scripts();
ExtendGDScriptParser *get_parse_successed_script(const String &p_path);
ExtendGDScriptParser *get_parse_result(const String &p_path);
void list_script_files(const String &p_root_dir, List<String> &r_files);
void apply_new_signal(Object *obj, String function, PackedStringArray args);
public:
String root;
String root_uri;
HashMap<String, ExtendGDScriptParser *> scripts;
HashMap<String, ExtendGDScriptParser *> parse_results;
HashMap<StringName, ClassMembers> native_members;
public:
Error initialize();
Error parse_script(const String &p_path, const String &p_content);
Error parse_local_script(const String &p_path);
String get_file_path(const String &p_uri) const;
String get_file_uri(const String &p_path) const;
void publish_diagnostics(const String &p_path);
void completion(const lsp::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options);
const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_required = false);
void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list);
const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
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);
Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name);
bool can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range);
Vector<lsp::Location> find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path);
Vector<lsp::Location> find_all_usages(const lsp::DocumentSymbol &p_symbol);
GDScriptWorkspace();
~GDScriptWorkspace();
};
#endif // GDSCRIPT_WORKSPACE_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,228 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "register_types.h"
#include "gdscript.h"
#include "gdscript_analyzer.h"
#include "gdscript_cache.h"
#include "gdscript_tokenizer.h"
#include "gdscript_tokenizer_buffer.h"
#include "gdscript_utility_functions.h"
#ifdef TOOLS_ENABLED
#include "editor/gdscript_highlighter.h"
#include "editor/gdscript_translation_parser_plugin.h"
#ifndef GDSCRIPT_NO_LSP
#include "language_server/gdscript_language_server.h"
#endif
#endif // TOOLS_ENABLED
#ifdef TESTS_ENABLED
#include "tests/test_gdscript.h"
#endif
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/io/file_access_encrypted.h"
#include "core/io/resource_loader.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_translation_parser.h"
#include "editor/export/editor_export.h"
#ifndef GDSCRIPT_NO_LSP
#include "core/config/engine.h"
#endif
#endif // TOOLS_ENABLED
#ifdef TESTS_ENABLED
#include "tests/test_macros.h"
#endif
GDScriptLanguage *script_language_gd = nullptr;
Ref<ResourceFormatLoaderGDScript> resource_loader_gd;
Ref<ResourceFormatSaverGDScript> resource_saver_gd;
GDScriptCache *gdscript_cache = nullptr;
#ifdef TOOLS_ENABLED
Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin;
class EditorExportGDScript : public EditorExportPlugin {
GDCLASS(EditorExportGDScript, EditorExportPlugin);
static constexpr int DEFAULT_SCRIPT_MODE = EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED;
int script_mode = DEFAULT_SCRIPT_MODE;
protected:
virtual void _export_begin(const HashSet<String> &p_features, bool p_debug, const String &p_path, int p_flags) override {
script_mode = DEFAULT_SCRIPT_MODE;
const Ref<EditorExportPreset> &preset = get_export_preset();
if (preset.is_valid()) {
script_mode = preset->get_script_export_mode();
}
}
virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) override {
if (p_path.get_extension() != "gd" || script_mode == EditorExportPreset::MODE_SCRIPT_TEXT) {
return;
}
Vector<uint8_t> file = FileAccess::get_file_as_bytes(p_path);
if (file.is_empty()) {
return;
}
String source;
source.parse_utf8(reinterpret_cast<const char *>(file.ptr()), file.size());
GDScriptTokenizerBuffer::CompressMode compress_mode = script_mode == EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED ? GDScriptTokenizerBuffer::COMPRESS_ZSTD : GDScriptTokenizerBuffer::COMPRESS_NONE;
file = GDScriptTokenizerBuffer::parse_code_string(source, compress_mode);
if (file.is_empty()) {
return;
}
add_file(p_path.get_basename() + ".gdc", file, true);
}
public:
virtual String get_name() const override { return "GDScript"; }
};
static void _editor_init() {
Ref<EditorExportGDScript> gd_export;
gd_export.instantiate();
EditorExport::get_singleton()->add_export_plugin(gd_export);
#ifdef TOOLS_ENABLED
Ref<GDScriptSyntaxHighlighter> gdscript_syntax_highlighter;
gdscript_syntax_highlighter.instantiate();
ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter);
#endif
#ifndef GDSCRIPT_NO_LSP
register_lsp_types();
GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer);
EditorNode::get_singleton()->add_editor_plugin(lsp_plugin);
Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton()));
#endif // !GDSCRIPT_NO_LSP
}
#endif // TOOLS_ENABLED
void initialize_gdscript_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
GDREGISTER_CLASS(GDScript);
script_language_gd = memnew(GDScriptLanguage);
ScriptServer::register_language(script_language_gd);
resource_loader_gd.instantiate();
ResourceLoader::add_resource_format_loader(resource_loader_gd);
resource_saver_gd.instantiate();
ResourceSaver::add_resource_format_saver(resource_saver_gd);
gdscript_cache = memnew(GDScriptCache);
GDScriptUtilityFunctions::register_functions();
}
#ifdef TOOLS_ENABLED
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
EditorNode::add_init_callback(_editor_init);
gdscript_translation_parser_plugin.instantiate();
EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
}
#endif // TOOLS_ENABLED
}
void uninitialize_gdscript_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
ScriptServer::unregister_language(script_language_gd);
if (gdscript_cache) {
memdelete(gdscript_cache);
}
if (script_language_gd) {
memdelete(script_language_gd);
}
ResourceLoader::remove_resource_format_loader(resource_loader_gd);
resource_loader_gd.unref();
ResourceSaver::remove_resource_format_saver(resource_saver_gd);
resource_saver_gd.unref();
GDScriptParser::cleanup();
GDScriptUtilityFunctions::unregister_functions();
}
#ifdef TOOLS_ENABLED
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
EditorTranslationParser::get_singleton()->remove_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
gdscript_translation_parser_plugin.unref();
}
#endif // TOOLS_ENABLED
}
#ifdef TESTS_ENABLED
void test_tokenizer() {
GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER);
}
void test_tokenizer_buffer() {
GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER_BUFFER);
}
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-tokenizer-buffer", &test_tokenizer_buffer);
REGISTER_TEST_COMMAND("gdscript-parser", &test_parser);
REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler);
REGISTER_TEST_COMMAND("gdscript-bytecode", &test_bytecode);
#endif

View file

@ -0,0 +1,39 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_REGISTER_TYPES_H
#define GDSCRIPT_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_gdscript_module(ModuleInitializationLevel p_level);
void uninitialize_gdscript_module(ModuleInitializationLevel p_level);
#endif // GDSCRIPT_REGISTER_TYPES_H

View file

@ -0,0 +1,58 @@
# GDScript integration tests
The `scripts/` folder contains integration tests in the form of GDScript files
and output files.
See the
[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html#integration-tests-for-gdscript)
for information about creating and running GDScript integration tests.
# GDScript Autocompletion tests
The `script/completion` folder contains test for the GDScript autocompletion.
Each test case consists of at least one `.gd` file, which contains the code, and one `.cfg` file, which contains expected results and configuration. Inside of the GDScript file the character `➡` represents the cursor position, at which autocompletion is invoked.
The script files won't be parsable GDScript since it contains an invalid char and and often the code is not complete during autocompletion. To allow for a valid base when used with a scene, the
runner will remove the line which contains `➡`. Therefore the scripts need to be valid if this line is removed, otherwise the test might behave in unexpected ways. This may for example require
adding an additional `pass` statement.
This also means, that the runner will add the script to its owner node, so the script should not be loaded through the scene file.
The config file contains two section:
`[input]` contains keys that configure the test environment. The following keys are possible:
- `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build.
- `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test.
- `add_node_path_literals: boolean = false`: Configures the corresponding editor setting for the test.
- `add_string_name_literals: boolean = false`: Configures the corresponding editor setting for the test.
- `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened.
- `node_path: String`: The node path of the node which holds the current script inside of the scene. Defaults to the scene root node.
`[output]` specifies the expected results for the test. The following key are supported:
- `include: Array`: An unordered list of suggestions that should be in the result. Each entry is one dictionary with the following keys: `display`, `insert_text`, `kind`, `location`, which correspond to the suggestion struct which is used in the code. The runner only tests against specified keys, so in most cases `display` will suffice.
- `exclude: Array`: An array of suggestions which should not be in the result. The entries take the same form as for `include`.
- `call_hint: String`: The expected call hint returned by autocompletion.
- `forced: boolean`: Whether autocompletion is expected to force opening a completion window.
Tests will only test against entries in `[output]` that were specified.
## Writing autocompletion tests
To avoid failing edge cases a certain behavior needs to be tested multiple times. Some things that tests should account for:
- All possible types: Test with all possible types that apply to the tested behavior. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.)
- `BUILTIN`
- `NATIVE`
- GDScripts (with `class_name` as well as `preload`ed)
- C# (as standin for all other language bindings) (with `class_name` as well as `preload`ed)
- Autoloads
- Possible contexts: the completion might be placed in different places of the program. e.g:
- initializers of class members
- directly inside a suite
- assignments inside a suite
- as parameter to a call

View file

@ -0,0 +1,729 @@
/**************************************************************************/
/* gdscript_test_runner.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "../gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/core_globals.h"
#include "core/io/dir_access.h"
#include "core/io/file_access_pack.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() {
HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
// First pass, add the constants so they exist before any script is loaded.
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
const ProjectSettings::AutoloadInfo &info = E.value;
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 (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
const ProjectSettings::AutoloadInfo &info = E.value;
if (!info.is_singleton) {
// Skip non-singletons since we don't have a scene tree here anyway.
continue;
}
Node *n = nullptr;
if (ResourceLoader::get_resource_type(info.path) == "PackedScene") {
// Cache the scene reference before loading it (for cyclic references)
Ref<PackedScene> scn;
scn.instantiate();
scn->set_path(info.path);
scn->reload_from_file();
ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
if (scn.is_valid()) {
n = scn->instantiate();
}
} else {
Ref<Resource> res = ResourceLoader::load(info.path);
ERR_CONTINUE_MSG(res.is_null(), vformat("Failed to instantiate an autoload, can't load from path: %s.", info.path));
Ref<Script> scr = res;
if (scr.is_valid()) {
StringName ibt = scr->get_instance_base_type();
bool valid_type = ClassDB::is_parent_class(ibt, "Node");
ERR_CONTINUE_MSG(!valid_type, vformat("Failed to instantiate an autoload, script '%s' does not inherit from 'Node'.", info.path));
Object *obj = ClassDB::instantiate(ibt);
ERR_CONTINUE_MSG(!obj, vformat("Failed to instantiate an autoload, cannot instantiate '%s'.", ibt));
n = Object::cast_to<Node>(obj);
n->set_script(scr);
}
}
ERR_CONTINUE_MSG(!n, vformat("Failed to instantiate an autoload, path is not pointing to a scene or a script: %s.", 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, bool p_print_filenames, bool p_use_binary_tokens) {
test_function_name = StaticCString::create("test");
do_init_languages = p_init_language;
print_filenames = p_print_filenames;
binary_tokens = p_use_binary_tokens;
source_dir = p_source_dir;
if (!source_dir.ends_with("/")) {
source_dir += "/";
}
if (do_init_languages) {
init_language(p_source_dir);
}
#ifdef DEBUG_ENABLED
// Set all warning levels to "Warn" in order to test them properly, even the ones that default to error.
ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
if (i == GDScriptWarning::UNTYPED_DECLARATION || i == GDScriptWarning::INFERRED_DECLARATION) {
// TODO: Add ability for test scripts to specify which warnings to enable/disable for testing.
continue;
}
String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i);
ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN);
}
#endif
// Enable printing to show results
CoreGlobals::print_line_enabled = true;
CoreGlobals::print_error_enabled = true;
}
GDScriptTestRunner::~GDScriptTestRunner() {
test_function_name = StringName();
if (do_init_languages) {
finish_language();
}
}
#ifndef DEBUG_ENABLED
static String strip_warnings(const String &p_expected) {
// On release builds we don't have warnings. Here we remove them from the output before comparison
// so it doesn't fail just because of difference in warnings.
String expected_no_warnings;
for (String line : p_expected.split("\n")) {
if (line.begins_with(">> ")) {
continue;
}
expected_no_warnings += line + "\n";
}
return expected_no_warnings.strip_edges() + "\n";
}
#endif
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];
if (print_filenames) {
print_line(test.get_source_relative_filepath());
}
GDScriptTest::TestResult result = test.run_test();
String expected = FileAccess::get_file_as_string(test.get_output_file());
#ifndef DEBUG_ENABLED
expected = strip_warnings(expected);
#endif
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++) {
GDScriptTest test = tests[i];
if (print_filenames) {
print_line(test.get_source_relative_filepath());
} else {
OS::get_singleton()->print(".");
}
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;
Ref<DirAccess> 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 == "completion" || next == "lsp") {
next = dir->get_next();
continue;
}
if (!make_tests_for_dir(current_dir.path_join(next))) {
return false;
}
} else {
if (next.ends_with(".notest.gd")) {
next = dir->get_next();
continue;
} else if (binary_tokens && next.ends_with(".textonly.gd")) {
next = dir->get_next();
continue;
} else if (next.get_extension().to_lower() == "gd") {
#ifndef DEBUG_ENABLED
// On release builds, skip tests marked as debug only.
Error open_err = OK;
Ref<FileAccess> script_file(FileAccess::open(current_dir.path_join(next), FileAccess::READ, &open_err));
if (open_err != OK) {
ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next));
next = dir->get_next();
continue;
} else {
if (script_file->get_line() == "#debug-only") {
next = dir->get_next();
continue;
}
}
#endif
String out_file = next.get_basename() + ".out";
ERR_FAIL_COND_V_MSG(!is_generating && !dir->file_exists(out_file), false, "Could not find output file for " + next);
if (next.ends_with(".bin.gd")) {
// Test text mode first.
GDScriptTest text_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
tests.push_back(text_test);
// Test binary mode even without `--use-binary-tokens`.
GDScriptTest bin_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
bin_test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
tests.push_back(bin_test);
} else {
GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir);
if (binary_tokens) {
test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER);
}
tests.push_back(test);
}
}
}
next = dir->get_next();
}
dir->list_dir_end();
return true;
}
bool GDScriptTestRunner::make_tests() {
Error err = OK;
Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
return make_tests_for_dir(dir->get_current_dir());
}
static bool generate_class_index_recursive(const String &p_dir) {
Error err = OK;
Ref<DirAccess> 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();
StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name();
while (!next.is_empty()) {
if (dir->current_is_dir()) {
if (next == "." || next == ".." || next == "completion" || next == "lsp") {
next = dir->get_next();
continue;
}
if (!generate_class_index_recursive(current_dir.path_join(next))) {
return false;
}
} else {
if (!next.ends_with(".gd")) {
next = dir->get_next();
continue;
}
String base_type;
String source_file = current_dir.path_join(next);
String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(source_file, &base_type);
if (class_name.is_empty()) {
next = dir->get_next();
continue;
}
ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false,
"Class name '" + class_name + "' from " + source_file + " is already used in " + ScriptServer::get_global_class_path(class_name));
ScriptServer::add_global_class(class_name, base_type, gdscript_name, source_file);
}
next = dir->get_next();
}
dir->list_dir_end();
return true;
}
bool GDScriptTestRunner::generate_class_index() {
Error err = OK;
Ref<DirAccess> dir(DirAccess::open(source_dir, &err));
ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory.");
source_dir = dir->get_current_dir() + "/"; // Make it absolute path.
return generate_class_index_recursive(dir->get_current_dir());
}
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();
for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
String &cmd = E->get();
if (cmd == "--gdscript-generate-tests") {
String path;
if (E->next()) {
path = E->next()->get();
} else {
path = "modules/gdscript/tests/scripts";
}
GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr);
bool completed = runner.generate_outputs();
int 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, bool p_rich) {
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, bool p_editor_notify, 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(">> ");
// Only include the function, file and line for script errors, otherwise the
// test outputs changes based on the platform/compiler.
bool include_source_info = false;
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");
include_source_info = true;
break;
case ERR_HANDLER_SHADER:
builder.append("SHADER ERROR");
break;
default:
builder.append("Unknown error type");
break;
}
if (include_source_info) {
builder.append("\n>> on function: ");
builder.append(String::utf8(p_function));
builder.append("()\n>> ");
builder.append(String::utf8(p_file).trim_prefix(self->base_dir).replace("\\", "/"));
builder.append("\n>> ");
builder.append(itos(p_line));
}
builder.append("\n>> ");
builder.append(String::utf8(p_error));
if (strlen(p_explanation) > 0) {
builder.append("\n>> ");
builder.append(String::utf8(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.
#ifndef DEBUG_ENABLED
expected = strip_warnings(expected);
#endif
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();
result.passed = false;
Error err = OK;
// Create script.
Ref<GDScript> script;
script.instantiate();
script->set_path(source_file);
if (tokenizer_mode == TOKENIZER_TEXT) {
err = script->load_source_code(source_file);
} else {
String code = FileAccess::get_file_as_string(source_file, &err);
if (!err) {
Vector<uint8_t> buffer = GDScriptTokenizerBuffer::parse_code_string(code, GDScriptTokenizerBuffer::COMPRESS_ZSTD);
script->set_binary_tokens_source(buffer);
}
}
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;
if (tokenizer_mode == TOKENIZER_TEXT) {
err = parser.parse(script->get_source_code(), source_file, false);
} else {
err = parser.parse_binary(script->get_binary_tokens_source(), source_file);
}
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();
if (!errors.is_empty()) {
// Only the first error since the following might be cascading.
result.output += errors.front()->get().message + "\n"; // TODO: line, column?
}
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();
if (!errors.is_empty()) {
// Only the first error since the following might be cascading.
result.output += errors.front()->get().message + "\n"; // TODO: line, column?
}
if (!p_is_generating) {
result.passed = check_output(result.output);
}
return result;
}
#ifdef DEBUG_ENABLED
StringBuilder warning_string;
for (const GDScriptWarning &E : parser.get_warnings()) {
const GDScriptWarning warning = E;
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();
#endif
// 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() + "\n";
if (!p_is_generating) {
result.passed = check_output(result.output);
}
return result;
}
// Script files matching this pattern are allowed to not contain a test() function.
if (source_file.match("*.notest.gd")) {
enable_stdout();
result.passed = check_output(result.output);
return result;
}
// Test running.
const HashMap<StringName, GDScriptFunction *>::ConstIterator test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name);
if (!test_function_element) {
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 + "'");
}
// 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);
err = script->reload();
if (err) {
enable_stdout();
result.status = GDTEST_LOAD_ERROR;
result.output = "";
result.passed = false;
ERR_FAIL_V_MSG(result, "\nCould not reload script: '" + source_file + "'");
}
// Create object instance for test.
Object *obj = ClassDB::instantiate(script->get_native()->get_name());
Ref<RefCounted> obj_ref;
if (obj->is_ref_counted()) {
obj_ref = Ref<RefCounted>(Object::cast_to<RefCounted>(obj));
}
obj->set_script(script);
GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance());
// Call test function.
Callable::CallError call_err;
instance->callp(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();
GDScriptCache::remove_script(script->get_path());
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;
Ref<FileAccess> 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);
return true;
}
} // namespace GDScriptTests

View file

@ -0,0 +1,140 @@
/**************************************************************************/
/* gdscript_test_runner.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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_H
#define GDSCRIPT_TEST_RUNNER_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;
};
enum TokenizerMode {
TOKENIZER_TEXT,
TOKENIZER_BUFFER,
};
private:
struct ErrorHandlerData {
TestResult *result = nullptr;
GDScriptTest *self = nullptr;
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;
TokenizerMode tokenizer_mode = TOKENIZER_TEXT;
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, bool p_rich);
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, bool p_editor_notify, ErrorHandlerType p_type);
TestResult run_test();
bool generate_output();
const String &get_source_file() const { return source_file; }
const String get_source_relative_filepath() const { return source_file.trim_prefix(base_dir); }
const String &get_output_file() const { return output_file; }
void set_tokenizer_mode(TokenizerMode p_tokenizer_mode) { tokenizer_mode = p_tokenizer_mode; }
TokenizerMode get_tokenizer_mode() const { return tokenizer_mode; }
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 print_filenames; // Whether filenames should be printed when generated/running tests
bool binary_tokens; // Test with buffer tokenizer.
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, bool p_print_filenames = false, bool p_use_binary_tokens = false);
~GDScriptTestRunner();
};
} // namespace GDScriptTests
#endif // GDSCRIPT_TEST_RUNNER_H

View file

@ -0,0 +1,109 @@
/**************************************************************************/
/* gdscript_test_runner_suite.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 {
// TODO: Handle some cases failing on release builds. See: https://github.com/godotengine/godot/pull/88452
#ifdef TOOLS_ENABLED
TEST_SUITE("[Modules][GDScript]") {
TEST_CASE("Script compilation and runtime") {
bool print_filenames = OS::get_singleton()->get_cmdline_args().find("--print-filenames") != nullptr;
bool use_binary_tokens = OS::get_singleton()->get_cmdline_args().find("--use-binary-tokens") != nullptr;
GDScriptTestRunner runner("modules/gdscript/tests/scripts", true, print_filenames, use_binary_tokens);
int fail_count = runner.run_tests();
INFO("Make sure `*.out` files have expected results.");
REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass.");
}
}
TEST_CASE("[Modules][GDScript] Load source code dynamically and run it") {
Ref<GDScript> gdscript = memnew(GDScript);
gdscript->set_source_code(R"(
extends RefCounted
func _init():
set_meta("result", 42)
)");
// A spurious `Condition "err" is true` message is printed (despite parsing being successful and returning `OK`).
// Silence it.
ERR_PRINT_OFF;
const Error error = gdscript->reload();
ERR_PRINT_ON;
CHECK_MESSAGE(error == OK, "The script should parse successfully.");
// Run the script by assigning it to a reference-counted object.
Ref<RefCounted> ref_counted = memnew(RefCounted);
ref_counted->set_script(gdscript);
CHECK_MESSAGE(int(ref_counted->get_meta("result")) == 42, "The script should assign object metadata successfully.");
}
#endif // TOOLS_ENABLED
TEST_CASE("[Modules][GDScript] Validate built-in API") {
GDScriptLanguage *lang = GDScriptLanguage::get_singleton();
// Validate methods.
List<MethodInfo> builtin_methods;
lang->get_public_functions(&builtin_methods);
SUBCASE("[Modules][GDScript] Validate built-in methods") {
for (const MethodInfo &mi : builtin_methods) {
int i = 0;
for (List<PropertyInfo>::ConstIterator itr = mi.arguments.begin(); itr != mi.arguments.end(); ++itr, ++i) {
TEST_COND((itr->name.is_empty() || itr->name.begins_with("_unnamed_arg")),
vformat("Unnamed argument in position %d of built-in method '%s'.", i, mi.name));
}
}
}
// Validate annotations.
List<MethodInfo> builtin_annotations;
lang->get_public_annotations(&builtin_annotations);
SUBCASE("[Modules][GDScript] Validate built-in annotations") {
for (const MethodInfo &ai : builtin_annotations) {
int i = 0;
for (List<PropertyInfo>::ConstIterator itr = ai.arguments.begin(); itr != ai.arguments.end(); ++itr, ++i) {
TEST_COND((itr->name.is_empty() || itr->name.begins_with("_unnamed_arg")),
vformat("Unnamed argument in position %d of built-in annotation '%s'.", i, ai.name));
}
}
}
}
} // namespace GDScriptTests
#endif // GDSCRIPT_TEST_RUNNER_SUITE_H

View file

@ -0,0 +1,2 @@
# Ignore metadata if someone open this on Godot.
/.godot

View file

@ -0,0 +1,2 @@
func test():
InstancePlaceholder.new()

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Native class "InstancePlaceholder" cannot be constructed as it is abstract.

View file

@ -0,0 +1,9 @@
class A extends InstancePlaceholder:
func _init():
print('no')
class B extends A:
pass
func test():
B.new()

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "InstancePlaceholder".

View file

@ -0,0 +1,6 @@
var num := 1
@export_range(num, 10) var a
func test():
pass

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Argument 1 of annotation "@export_range" isn't a constant expression.

View file

@ -0,0 +1,3 @@
enum { V }
func test():
V = 1

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.

View file

@ -0,0 +1,3 @@
enum NamedEnum { V }
func test():
NamedEnum.V = 1

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.

View file

@ -0,0 +1,4 @@
signal your_base
signal my_base
func test():
your_base = my_base

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a constant.

View file

@ -0,0 +1,4 @@
func test():
var tree := SceneTree.new()
tree.root = Window.new()
tree.free()

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a read-only property.

View file

@ -0,0 +1,4 @@
func test():
var state := PhysicsDirectBodyState3DExtension.new()
state.center_of_mass.x += 1.0
state.free()

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a new value to a read-only property.

View file

@ -0,0 +1,3 @@
func test():
var var_color: String = Color.RED
print('not ok')

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a value of type "Color" as "String".

View file

@ -0,0 +1,4 @@
signal my_signal()
func test():
var _a := await my_signal

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot infer the type of "_a" variable because the value doesn't have a set type.

View file

@ -0,0 +1,3 @@
func test():
# Error here.
print(2.2 << 4)

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid operands to operator <<, float and int.

View file

@ -0,0 +1,3 @@
func test():
# Error here.
print(2 >> 4.4)

View file

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid operands to operator >>, int and float.

View file

@ -0,0 +1,7 @@
# GH-73283
class MyClass:
pass
func test():
MyClass.not_existing_method()

Some files were not shown because too many files have changed in this diff Show more