Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GDScript support #3194

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Tmain/list-fields-with-prefix.d/stdout-expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ x UCTAGSxpath no NONE s-- no -- xpath for
- UCTAGSmacrodef no CUDA s-- no -- macro definition
- UCTAGSproperties no CUDA s-- no -- properties (static, inline, mutable,...)
- UCTAGSaccess yes Elixir s-- no -- access
- UCTAGSannotations yes GDScript s-- no -- annotations on functions and variables
- UCTAGSnameref yes GDScript s-- no -- the original name for the tag
- UCTAGShowImported no Go s-- no -- how the package is imported ("inline" for `.' or "init" for `_')
- UCTAGSpackage yes Go s-- no -- the real package specified by the package name
- UCTAGSpackageName yes Go s-- no -- the name for referring the package
Expand Down
2 changes: 2 additions & 0 deletions Tmain/list-fields.d/stdout-expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ z kind no NONE s-- no r- [tags output] prepend "kind:" to k/ (or K/) field outpu
- macrodef no CUDA s-- no -- macro definition
- properties no CUDA s-- no -- properties (static, inline, mutable,...)
- access yes Elixir s-- no -- access
- annotations yes GDScript s-- no -- annotations on functions and variables
- nameref yes GDScript s-- no -- the original name for the tag
- howImported no Go s-- no -- how the package is imported ("inline" for `.' or "init" for `_')
- package yes Go s-- no -- the real package specified by the package name
- packageName yes Go s-- no -- the name for referring the package
Expand Down
1 change: 1 addition & 0 deletions Units/parser-gdscript.r/sample.d/args.ctags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--sort=no
26 changes: 26 additions & 0 deletions Units/parser-gdscript.r/sample.d/expected.tags
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
a input.gd /^@export_range(start=0, end=100, step=1) var a = 5$/;" v annotations:export_range(start=0, end=100, step=1)
s input.gd /^var s = "Hello"$/;" v annotations:export
arr input.gd /^@onready var arr = [1, 2, 3]$/;" v annotations:onready
dict input.gd /^var dict = {"key": "value", 2: 3}$/;" v
typed_var input.gd /^var typed_var: int$/;" v typeref:typename:int
inferred_type input.gd /^inferred_type\\$/;" v annotations:onready,export_multiline
ANSWER input.gd /^const ANSWER = 42$/;" v typeref:typename:const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one, ANSWER is tagged as a v, a variable.
However, I think it should be tagged as a const or constant.
How do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just followed the example of the first file with a constant variable that I saw in a grep, I went by parser-cxx.r/cxx11-raw-strings.d/expected.tags, which has:

12:str4	input.cpp	/^static const char* str4 = LR"blah(";int bug4;)blah";$/;"	v	typeref:typename:const char *	file:
13:str5	input.cpp	/^static const char* str5 = u8R"blah(";int bug5;)blah";$/;"	v	typeref:typename:const char *	file:
14:str6	input.cpp	/^static const char* str6 = uR"blah(";int bug6;)blah";$/;"	v	typeref:typename:const char *	file:
15:str7	input.cpp	/^static const char* str7 = UR"blah(";int bug7;)blah";$/;"	v	typeref:typename:const char *	file:

Which classifies them with v and puts const in the type, which I followed. GDScript does do a thing where you substitute the keyword var entirely with the word const to declare a constant, but it's functionally the same as a constant variable I'd think

Copy link
Contributor Author

@Davidy22 Davidy22 Nov 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I could drop into godot dev chat real quick and get a thought from there from someone who might have written const on which to prefer real quick

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are developers think differently.

constant kind is used in https://github.com/syskrank/vim-gdscript-ctags .
C kind is used for constants in https://github.com/PrestonKnopp/gdtags .

C/C++ parser takes a different approach. Base on the target language's sematic, the parser recognizes const is a part of a type.

$ cat /tmp/foo.c
const int i = 1;
$ ./ctags -o - /tmp/foo.c
i	/tmp/foo.c	/^const int i = 1;$/;"	v	typeref:typename:const int

ctags is a low-level tool. If possible, ctags should represents different things in different ways. This is one of the mottos of u-ctags.
If you want you can map "const" kind items extracted in ctags to "variable" kind items in geany.

A questionable thing is "const" defined in a method. What kind we should use for it? My idea is just using "const".
Instead, we can delete the "local" kind. You may think deleting "local" kind conflicts with the motto.
However, it is acceptable because the scope: field of a tag for a local variable can represent the difference.

Let's think about the followig input:

$ cat /tmp/foo.gd
var v
const c = 1
func _init():
    var lv = Something.new()
	const lc = 2

The current parser output:

$ ./ctags --sort=no --kinds-GDScript='*' --fields=+K -o - /tmp/foo.gd
v	/tmp/foo.gd	/^var v$/;"	variable
c	/tmp/foo.gd	/^const c = 1$/;"	variable	typeref:typename:const
_init	/tmp/foo.gd	/^func _init():$/;"	function
lv	/tmp/foo.gd	/^    var lv = Something.new()$/;"	local	function:_init	file:
lc	/tmp/foo.gd	/^	const lc = 2$/;"	local	function:_init	typeref:typename:const	file:

My idea:

$ ./new-ctags --sort=no --kinds-GDScript='*' --fields=+K -o - /tmp/foo.gd
v	/tmp/foo.gd	/^var v$/;"	variable
c	/tmp/foo.gd	/^const c = 1$/;"	constant
_init	/tmp/foo.gd	/^func _init():$/;"	function
lv	/tmp/foo.gd	/^    var lv = Something.new()$/;"	variable	function:_init	file:
lc	/tmp/foo.gd	/^	const lc = 2$/;"	constant	function:_init	file:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the local type gets wiped in favor of metadata I'll have to figure out how to look at metadata to exclude locals from the symbol table in geany, otherwise this is probably fine I guess. Still feel like the relationship between const and var is more akin to C behavior despite the keywording though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here ctags is built from the source code I proposed in #3194.

Consider the following as input:

$ cat -n /tmp/foo.gd
     1	var x
     2	func f(x):
     3		var x
     4		func g(x):
     5			var x
     6			

ctags-#3194's output:

$ u-ctags --sort=no --fields=+n -o - /tmp/foo.gd 
anon_class_49f7b8910100	/tmp/foo.gd	/^var x$/;"	c	line:1
x	/tmp/foo.gd	/^var x$/;"	v	line:1	class:anon_class_49f7b8910100
f	/tmp/foo.gd	/^func f(x):$/;"	m	line:2	class:anon_class_49f7b8910100
x	/tmp/foo.gd	/^func f(x):$/;"	z	line:2	method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	var x$/;"	l	line:3	method:anon_class_49f7b8910100.f	file:
g	/tmp/foo.gd	/^	func g(x):$/;"	f	line:4	method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	func g(x):$/;"	z	line:4	function:anon_class_49f7b8910100.f.g	file:
x	/tmp/foo.gd	/^		var x$/;"	l	line:5	function:anon_class_49f7b8910100.f.g	file:

--fields=+n option is for attaching line: fields:

If we add --fields=+Z option to the command line, scope: field markers are added. It helps us understand tags file:

$ u-ctags --sort=no --fields=+nZ -o - /tmp/foo.gd 
anon_class_49f7b8910100	/tmp/foo.gd	/^var x$/;"	c	line:1
x	/tmp/foo.gd	/^var x$/;"	v	line:1	scope:class:anon_class_49f7b8910100
f	/tmp/foo.gd	/^func f(x):$/;"	m	line:2	scope:class:anon_class_49f7b8910100
x	/tmp/foo.gd	/^func f(x):$/;"	z	line:2	scope:method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	var x$/;"	l	line:3	scope:method:anon_class_49f7b8910100.f	file:
g	/tmp/foo.gd	/^	func g(x):$/;"	f	line:4	scope:method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	func g(x):$/;"	z	line:4	scope:function:anon_class_49f7b8910100.f.g	file:
x	/tmp/foo.gd	/^		var x$/;"	l	line:5	scope:function:anon_class_49f7b8910100.f.g	file:

If we add --fields=+ZK option to the command line, the long-name of kinds are used. It helps us understand tags file:

$ u-ctags --sort=no --fields=+nZK -o - /tmp/foo.gd 
anon_class_49f7b8910100	/tmp/foo.gd	/^var x$/;"	class	line:1
x	/tmp/foo.gd	/^var x$/;"	variable	line:1	scope:class:anon_class_49f7b8910100
f	/tmp/foo.gd	/^func f(x):$/;"	method	line:2	scope:class:anon_class_49f7b8910100
x	/tmp/foo.gd	/^func f(x):$/;"	parameter	line:2	scope:method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	var x$/;"	local	line:3	scope:method:anon_class_49f7b8910100.f	file:
g	/tmp/foo.gd	/^	func g(x):$/;"	function	line:4	scope:method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	func g(x):$/;"	parameter	line:4	scope:function:anon_class_49f7b8910100.f.g	file:
x	/tmp/foo.gd	/^		var x$/;"	local	line:5	scope:function:anon_class_49f7b8910100.f.g	file:

The question is how it will be if we use variable kind instead of local. Let's replace "local" with "variable" with a text editor:

anon_class_49f7b8910100	/tmp/foo.gd	/^var x$/;"	class	line:1
x	/tmp/foo.gd	/^var x$/;"	variable	line:1	scope:class:anon_class_49f7b8910100
f	/tmp/foo.gd	/^func f(x):$/;"	method	line:2	scope:class:anon_class_49f7b8910100
x	/tmp/foo.gd	/^func f(x):$/;"	parameter	line:2	scope:method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	var x$/;"	variable	line:3	scope:method:anon_class_49f7b8910100.f	file:
g	/tmp/foo.gd	/^	func g(x):$/;"	function	line:4	scope:method:anon_class_49f7b8910100.f	file:
x	/tmp/foo.gd	/^	func g(x):$/;"	parameter	line:4	scope:function:anon_class_49f7b8910100.f.g	file:
x	/tmp/foo.gd	/^		var x$/;"	variable	line:5	scope:function:anon_class_49f7b8910100.f.g	file:

Both using the kind information (parameter or variable) and the scope fields, you can distinguish all x.

So I think deleting the local kind is acceptable in your use case.
However, keeping is o.k. My original suggestion was introducing "const" kind.

THE_NAME input.gd /^const THE_NAME:String = "Charly"$/;" v typeref:typename:const String
anon_enum_e3cc11790105 input.gd /^enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALL}$/;" g
Named input.gd /^enum Named {THING_1, THING_2, ANOTHER_THING=1}$/;" g
v2 input.gd /^var v2 = Vector2(1, 2)$/;" v
v3 input.gd /^var v3 = Vector3(1, 2, 3)$/;" v
some_function input.gd /^func some_function(param1: Vector3, param2: int) -> int:$/;" f typeref:typename:int annotations:master
param1 input.gd /^func some_function(param1: Vector3, param2: int) -> int:$/;" z function:some_function typeref:typename:Vector3 file:
param2 input.gd /^func some_function(param1: Vector3, param2: int) -> int:$/;" z function:some_function typeref:typename:int file:
local_var input.gd /^ var local_var = 5$/;" l function:some_function file:
local_var2 input.gd /^ var local_var2 = param1 + 3$/;" l function:some_function file:
something input.gd /^func something(p1, p2):$/;" f annotations:puppet
p1 input.gd /^func something(p1, p2):$/;" z function:something file:
p2 input.gd /^func something(p1, p2):$/;" z function:something file:
Something input.gd /^class Something:$/;" c
a input.gd /^ var a = 10$/;" v class:Something
_private_var input.gd /^ const _private_var:String = "hi\\n\\\\escape"$/;" v class:Something typeref:typename:const String
foooooooo input.gd /^ func foooooooo() -> String:$/;" m class:Something typeref:typename:String
_init input.gd /^func _init():$/;" f
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test case for method, module, enumerator, parameter, and local?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have lines that would be related to module, enumerator, parameter and local, not showing up in the test output though. Module is extends (kind of), enumerator is by the two enums, there's some functions with parameters and a var local_var = 5 in some_function. Can add a method to the class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put

--kinds-GDScript=+{parameter}
--kinds-GDScript=+{local}

to the args.ctags file.

lv input.gd /^ var lv = Something.new()$/;" l function:_init file:
100 changes: 100 additions & 0 deletions Units/parser-gdscript.r/sample.d/input.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Derived from https://docs.godotengine.org/en/latest/tutorials/scripting/gdscript/gdscript_basics.html

# A file is a class!

# Inheritance

extends BaseClass

# (optional) class definition with a custom icon

class_name MyClass, "res://path/to/optional/icon.svg"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder MyClass should be tagged with "class" kind?
How do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class_name keyword in gdscript is a little bit weird. Actually, all files in GDScript are by default sort of anonymous classes. extends is class inheritance for files, and class_name sets a name for the class that the file defines. The identifier, which in this case is MyClass, is actually undefined inside the file where class_name is used. Instead, class_name automatically includes the file as a class in all other files in the project, in this case using MyClass as the identifier, without the need for any explicit extends or preload. If we actually try to use the MyClass identifier within this file though, Godot will throw an error and you probably don't ever need to try reference MyClass from inside MyClass anyways because you can just directly refer to other variables and functions in here.

This actually means that the parser I've submitted is slightly incomplete, because it also needs to crawl the rest of the directory for .gd files with class_name declarations to list out every class that will be available in a file because GDScript can include files without you saying that you wanted to import anything, but I thought this might be a little out of scope.



# Member variables

@export_range(start=0, end=100, step=1) var a = 5
@export
var s = "Hello"
@onready var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var typed_var: int
@onready
@export_multiline
var\
inferred_type\
:=\
"String"

# Constants

const ANSWER = 42
const THE_NAME:String = "Charly"

# Enums

enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALL}
enum Named {THING_1, THING_2, ANOTHER_THING=1}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that your parser has the ability to tag THING_1, THING_2,...
I wonder why they are not in expected.tags.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first handwritten test case had lines for the enumerated values because I thought they'd show up. Thought this was how it was supposed to be, they do show up in the geany symbols list, back to checking code then. Local variables in functions also don't show up outside


# Built-in vector types

var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)


# Function

@master
func some_function(param1: Vector3, param2: int) -> int:
var local_var = 5

if param1 < local_var:
print(param1)
elif param2 > 5:
print(param2)
elif param2 == 2:
print(20)
elif param2 <= 2:
print((-20 %



3) / 5)
else:
print("Fail!")

for i in range(20):
print(i)

while param2 != 0:
param2 -= 1

var local_var2 = param1 + 3
return local_var2


# Functions override functions with the same name on the base/parent class.
# If you still want to call them, use '.' (like 'super' in other languages).
@puppet
func something(p1, p2):
.something(p1, p2)


# Inner class

class Something:
var a = 10
const _private_var:String = "hi\n\\escape"
func foooooooo() -> String:
print("""
test\\

test""")
return ""

# Constructor

func _init():
print("Constructed!")
var lv = Something.new()
print(lv.a)
1 change: 1 addition & 0 deletions docs/news.rst
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ The following parsers have been added:
* Falcon
* FunctionParameters *perl based subparser*
* Gdbinit script *optlib*
* GDScript
* Glade *libxml*
* Go
* Haskell
Expand Down
1 change: 1 addition & 0 deletions main/parsers_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
FunctionParametersParser, \
FyppParser, \
GdbinitParser, \
GDScriptParser, \
GoParser, \
HaskellParser, \
HaxeParser, \
Expand Down
Loading