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

Add GDScript support #3194

wants to merge 1 commit into from

Conversation

Davidy22
Copy link
Contributor

Tested in geany, heavily borrowed from python

Used #2654 as reference somewhat, although I do notice there's some additional files that may be requested but the test case format looks fairly dense and I'm hoping the output files are generated and someone'll point to how to generate them.

parsers/gdscript.c Outdated Show resolved Hide resolved
parsers/gdscript.c Outdated Show resolved Hide resolved
@masatake
Copy link
Member

Thank you for the contribution.
See https://docs.ctags.io/en/latest/testing-parser.html about writing a test case.
Could you add a GDScript to docs/news.rst as a newly introduced parser?

@masatake
Copy link
Member

parsers/gdscript.c: In function 'constructParentString':
parsers/gdscript.c:426:23: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
    vStringPut(result, (int) e->name);
                       ^
parsers/gdscript.c: In function 'findVariable':
parsers/gdscript.c:541:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
     if (strncmp (ln, "var", 3) != 0)
     ^~
parsers/gdscript.c:544:2: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
  cp = strstr(ln, "=");
  ^~
parsers/gdscript.c: In function 'findGDScriptTags':
parsers/gdscript.c:661:28: warning: passing argument 2 of 'vStringPut' makes integer from pointer without a cast [-Wint-conversion]
   vStringPut(continuation, line);
                            ^~~~
In file included from ./main/field.h:22,
                 from ./main/entry.h:21,
                 from parsers/gdscript.c:17:
./main/vstring.h:105:64: note: expected 'int' but argument is of type 'const char *'
 CTAGS_INLINE void vStringPut (vString *const string, const int c)
                                                      ~~~~~~~~~~^
parsers/gdscript.c:645:27: warning: unused variable 'candidate' [-Wunused-variable]
   const char *cp = line, *candidate;
                           ^~~~~~~~~

@codecov
Copy link

codecov bot commented Nov 21, 2021

Codecov Report

Merging #3194 (58d1602) into master (f9c2d52) will increase coverage by 0.10%.
The diff coverage is 92.23%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3194      +/-   ##
==========================================
+ Coverage   84.79%   84.89%   +0.10%     
==========================================
  Files         205      206       +1     
  Lines       48195    48827     +632     
==========================================
+ Hits        40867    41454     +587     
- Misses       7328     7373      +45     
Impacted Files Coverage Δ
parsers/gdscript.c 92.23% <92.23%> (ø)
main/entry.c 88.57% <0.00%> (ø)
parsers/dts.c 100.00% <0.00%> (ø)
parsers/ldscript.c 86.75% <0.00%> (ø)
parsers/protobuf.c 94.79% <0.00%> (ø)
parsers/cxx/cxx_tag.c 90.32% <0.00%> (ø)
parsers/cxx/cxx_parser.c 85.55% <0.00%> (+0.02%) ⬆️
parsers/c.c 78.88% <0.00%> (+0.02%) ⬆️
parsers/cpreprocessor.c 94.06% <0.00%> (+0.27%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f9c2d52...58d1602. Read the comment docs.

@masatake
Copy link
Member

This is nothing to do with whether I will merge your changes or not. I will merge your changes anyway.
However, I have some words because I expect you will contribute to this project more in the future:-P

It seems that you use your master branch for developing your parser. However, I think you should make a topic branch for the purpose and use it. https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows may help you.

parsers/gdscript.c Outdated Show resolved Hide resolved
@masatake
Copy link
Member

It seems that you have developed GDScript parser based on a bit old Python parser.
The old one is a line-oriented parser.
The new one is a token-oriented parser.
See https://github.com/universal-ctags/ctags/blob/master/parsers/python.c.

Anyway, we can replace the implementation later.
What we need is a test case.

It seems that the first example in https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html is good for starting.

@Davidy22
Copy link
Contributor Author

Davidy22 commented Nov 21, 2021

Oh yeah, going to get to the test case, I just pushed a few times to get compile to pass before I had to go out for a bit, I'll take a run at test cases some time tonight when I get back.

I wrote and tested this from the Geany repo, grabbed the python parser from there too so I guess the python parser there is also the older one. I'll probably make a pull request to update the python ctags parser as well in Geany, I did notice some newer python features that don't get highlighted in Geany that might be because the python parser there is old. I can replace with a modified version of the new implementation too here, not in a rush to push old code when I can start this off with shiny new code instead.

@Davidy22
Copy link
Contributor Author

Took a minute, but redone from the newer version. The instructions for running test cases locally might be outdated, make doesn't have a units target on what I pulled, but I think it'll run in CI so can probably see if it passes new cases here.

I set enumerated values to K_VARIABLE because I couldn't for the life of me figure out how to get them to register on symbols list in geany, but that might be external to ctags. I'll take another run at it if the distinction is critical.

@masatake
Copy link
Member

Took a minute, but redone from the newer version.

Thank you.

The instructions for running test cases locally might be outdated, make doesn't have a units target on what I pulled,

Could you tell me the detail of the trouble?
The "units" test facility is the heart of u-ctags development. I would like to fix it.

but I think it'll run in CI so can probably see if it passes new cases here.

I set enumerated values to K_VARIABLE because I couldn't for the life of me figure out how to get them to register on symbols list in geany, but that might be external to ctags. I'll take another run at it if the distinction is critical.

As far as I heard, geany has mappings from u-ctags's kinds to geany's kind.
So, please define and use kinds that are natural and/or comfortable for GDScript developpers. The design of kinds is the most important part of a parser development. Rewritting a parser is always allowed. However, redesigning kinds is not allowed onece the code is merged to our repo.

@Davidy22
Copy link
Contributor Author

The command from this section of the unit test guide says to run make units, ran make units from the top level and it turned up nothing. I did a quick search in the repo and found a units target in makefiles/testing.mak, so I guess I just need to use something other than GNU make to run this locally.

But while I'm still looking at unit tests through CI, looks like there's some existing files to append to, I'll get to trying to get these to pass

Since all the other languages with enums use the enumerator kind in their enums I probably should be using that instead of variable, I'll take another run at it.

@masatake
Copy link
Member

The command from this section of the unit test guide says to run make units, ran make units from the top level and it turned up nothing.

Can I see the terminal output of "make units" ?

@Davidy22
Copy link
Contributor Author

Davidy22 commented Nov 25, 2021

make: *** No rule to make target 'units'.  Stop.

It's not much to go on, but at least github's not charging us for their CI so I can see the test results here. I'm running this from repo top level, where the readme and Makefile.am are.

Tests mostly fixed up I think, except for the one in list-roles-with-kind-names.d where it seems like something I've changed has wiped the whole output of that file so I'll need to figure that out

@masatake
Copy link
Member

$ make: *** No rule to make target 'units'.  Stop.

Reproduced:

$ cd /tmp
cd /tmp
$ git clone https://github.com/universal-ctags/ctags
git clone https://github.com/universal-ctags/ctags
Cloning into 'ctags'...
remote: Enumerating objects: 56326, done.        
remote: Counting objects: 100% (3757/3757), done.        
remote: Compressing objects: 100% (2008/2008), done.        
remote: Total 56326 (delta 1997), reused 3008 (delta 1645), pack-reused 52569        
Receiving objects: 100% (56326/56326), 18.77 MiB | 10.63 MiB/s, done.
Resolving deltas: 100% (35458/35458), done.
$ cd ctags
cd ctags
$ make units
make units
make: *** No rule to make target 'units'.  Stop.
$ 

How about doing following:

$ bash ./autogen.sh 
bash ./autogen.sh 
##################################################################
#                The paths and versions for tools                #
##################################################################
autoreconf is /usr/bin/autoreconf
------------------------------------------------------------------
...
$ ./configure
./configure
Universal Ctags, version 5.9.0
Linux 5.14.17-301.fc35.x86_64 #1 SMP Mon Nov 8 13:57:43 UTC 2021 x86_64
...
$ make
make
REPOINFO   main/repoinfo.h
make  all-recursive
make[1]: Entering directory '/tmp/ctags'
Making all in gnulib
...
$ make units
make units
REPOINFO   main/repoinfo.h
  CCLD     ctags
  RUN      units

Category: ROOT
------------------------------------------------------------
Testing vera-interface as Vera                              passed
Testing zephir-simple as Zephir                             passed
...

@masatake
Copy link
Member

masatake commented Nov 25, 2021

I'm inspecting the reason why 'make tmain' fails.
With your change, I got

$ ./ctags --quiet --options=NONE --list-roles=all.'{header}'
Segmentation fault (core dumped)

.

The kind definition of the new parser may be broken. See the next comment.

@masatake
Copy link
Member

The entries in GDScriptKinds and gdscriptKind are associated 1 to 1.
You defined K_METHOD in gdscriptKind, however no "method" in GDScriptKinds.
You defined K_MODULE in gdscriptKind, however no "module" in GDScriptKinds.
You defined K_PARAMETER in gdscriptKind, however no "parameter" in GDScriptKinds.
You defined K_LOCAL_VARIABLE in gdscriptKind, however no "localvar" in GDScriptKinds.

On the other hand, you defined "namespace" in GDScriptKinds. However, no K_NAMESPACE in GDScriptKinds.

COUNT_KIND is a special. You don't need an associated entry for it in GDScriptKinds.

Could you revise the kind definition for GDScript?

I have two more requests.

  1. Could you rename "Units/parser-gdscript.r/sample" to "Units/parser-gdscript.r/sample.d"?
  2. Could you add "args.ctags" file under "Units/parser-gdscript.r/". The new file must include a line "--sort=no".

@Davidy22
Copy link
Contributor Author

I've even built stuff that's used the autogen, configure steps and it didn't come to mind here. Well, got to run the tests, changed code till the test output looked right and then just used the test output as expected instead of trying to handwrite it.

Renamed, file added, and switched enumerated values back to K_ENUMERATOR, got them to show up in the geany editor too. Tests pass locally, I'm assuming they should pass in CI so now we should be on semantics.

something input.gd /^func something(p1, p2):$/;" f
Something input.gd /^class Something:$/;" c
a input.gd /^ var a = 10$/;" v class:Something
_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.


# (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.

ACCESS_PROTECTED,
ACCESS_PUBLIC,
COUNT_ACCESS
} accessType;
Copy link
Member

Choose a reason for hiding this comment

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

Does GDScript the concept "access" ?

If yes, could you add a test case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

GDScript borrowed python's logic of making anything with a name starting with an underscore_ protected, there's an _init() already in the test file. Are we expecting more information in the output for this?

Copy link
Member

Choose a reason for hiding this comment

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

Are we expecting more information in the output for this?

Yes. Let's the access information to expected.tags.
You can do it with putting

--fields=+{access}

to the args.ctags file.

parsers/gdscript.c Outdated Show resolved Hide resolved
# 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

parsers/gdscript.c Outdated Show resolved Hide resolved
@masatake
Copy link
Member

Valgrind reports some memory-leaks. You can run it locally with the command: "make units LANGUAGES=GDScript VG=1" if you installed valgrind locally.

The test coverage is still low. I think I can help you increase the coverage. Give me time.

@Davidy22
Copy link
Contributor Author

Satisfied valgrind, and the test case file is mutating. Thought anonGenerateNew was something different when it was recommended, it does make the enum names a little ugly but this ensures uniqueness I guess. I suspect this isn't going to push the test coverage over the threshold, maybe it does though.

Some files named thrift generated in peg/ when running tests could stand to get added to the gitignore maybe or just cleaned up post run, deleted them a good few times.

parsers/gdscript.c Outdated Show resolved Hide resolved
@Davidy22
Copy link
Contributor Author

Oh word just looked, it's so close, just need to find a little more to add

vStringClear(name->string);
vStringCatS(name->string, vStringValue(enumName));
vStringDelete(enumName);
name->type = TOKEN_IDENTIFIER;
Copy link
Member

Choose a reason for hiding this comment

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

You don't have to make a new vstring.
Instead, let's reuse name->string area.

diff --git a/parsers/gdscript.c b/parsers/gdscript.c
index f9f83f206..e912ea28f 100644
--- a/parsers/gdscript.c
+++ b/parsers/gdscript.c
@@ -918,10 +918,8 @@ static bool parseEnum (tokenInfo *const token)
 
        if (token->type == '{') {
                copyToken (name, token);
-               vString *enumName = anonGenerateNew ("anon_enum_", K_ENUM);
-               vStringClear(name->string);
-               vStringCatS(name->string, vStringValue(enumName));
-               vStringDelete(enumName);
+               vStringClear (name->string);
+               anonGenerate (name->string, "anon_enum_", K_ENUM);
                name->type = TOKEN_IDENTIFIER;
        } else if (token->type == TOKEN_IDENTIFIER) {
                copyToken (name, token);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, anonGenerate doing that makes it a good bit nicer

@Davidy22
Copy link
Contributor Author

Davidy22 commented Nov 27, 2021

Alright I flipped everything to true in GDScriptKinds and just changed some things on the geany symbols list side to make undesired things not show up, and made the whitespace in the test input file worse. parameter and local are still flipped to false in the python parser, so by default the python parser still should skip those. Output file now has parameters and local variables. I tried to type a carriage return, strangely difficult on linux, and it doesn't show up on git but I'm pretty sure enough more is being added to clear the test case threshold even if the carriage returns all got minced back into line feeds somewhere.

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.

@masatake
Copy link
Member

Current TODO items.

  • extends ad class_name keywords
    I wonder what we should do. I must learn GDScript to decide how we should do it.
    Once the decision is made, @masatake can implement anything we need about extends ad class_name keywords.
    This will be the most interesting area of this parser.

  • e/enumerator

    enum x {a, b}
    

    x is tagged. However, it seems that a and b are not tagged.
    @masatake may be able to implement what we need.

  • const
    constants are tagged as variables.
    Once the decision is made, @masatake can implement anything we need.

@masatake
Copy link
Member

I'm studying GDScript slowly.
@Davidy22, how do you think about signal? I think developers using GDScript may be happy if ctags can extract signals.
https://gdscript.com/solutions/signals-godot/

I don't say extracting signals is not a must item for merging your parser to the ctags repo.
I'm talking about the design of kinds. If kinds (and fields) are designed well, someone (including myself) can implement it even the one doesn't know GDScript. However, only a person who knows GDScript like you can design kinds and fields for the parser.

@masatake
Copy link
Member

The coverage is high enough. However, we can increase it more.
Could you add

--extras=+r
--fields=+r

to the args.ctags?

@Davidy22
Copy link
Contributor Author

Davidy22 commented Nov 27, 2021

Oh, signals probably would be pretty useful. I kind of passed over them because signals are referenced with strings as parameters to a lookup function as opposed to actual identifiers, variables/functions/etc, that usually make up the symbols list. It would be pretty useful to have though, if we're willing to overlook how signals aren't identifiers in the same way everything else is. Some things to deal with if we want to show all signals available in a file:

  • Signals can only travel along the node tree hierarchy, we'll need to parse the tscn file to find valid signals.
  • Signal names can be in the autoloaded files defined in project.godot
  • The above two points mean we're also crawling other .gd files to read their defined signals

Added the extra bit to args, I can take another run at enumerators sometime

Tested in geany, heavily borrowed from python
@Davidy22
Copy link
Contributor Author

Pulled back because something on ci test. I'll be back in a while to see what's going on.

@masatake
Copy link
Member

To enable the assertion, you must rebuild ctags:

./configure --enable-debugging
make clean
make

@masatake
Copy link
Member

You defined the function method and the method kinds. I guess only the method kind is needed. I would like to hear your comment.b

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 ""

So the class has no name, a class is being defined behind the seen.
Let's call it anonClass.

I guess something is a method of anonClass. Am I correct?
Something is a class defined in anonClass.

If with the class_name keyword, a developer can give a name for the class.

@masatake masatake mentioned this pull request Nov 27, 2021
14 tasks
@masatake
Copy link
Member

I convet my comments to C language:-). See #3199.

@Davidy22
Copy link
Contributor Author

Yeah, it is technically true that every function is a method because files are anonymous classes. GDScript docs use the words function and method in the typical sense though, and the distinction in calling functions and methods is like in other languages, function() as opposed to object.function(). The .something() in the example above calls something slightly differently because it's demonstrating syntax for calling the original function when overriding, it's actually not recursion. something(1,2) would be a legal call, and it would call the function defined above like you'd expect from a function, and foooooooo() would be illegal because it needs to be called from an object defined from class Something, like so:

var obj = Something.new()
print(obj.foooooooo())

Something is a class defined in the larger class file, yeah. We can keep nesting class definitions if we wanted to:

class Something:
    class Something1:
        class Something2:
            class Something3:
                class Something4:
                    class Something5:
                        var a

And because files are classes, even Something is already a class definition nested within the anonClass.

class_name gives the file a class name and automatically makes the class available in all other project files, so in order to get a complete list of all classes that could be instantiated in a file, we need to parse all the other .gd files present in the directory for class_name calls. Or maybe this is the job of the text editor to track open files with a class_name call.

@masatake
Copy link
Member

masatake commented Nov 29, 2021

class_name gives the file a class name and automatically makes the class available in all other project files, so in order to get a complete list of all classes that could be instantiated in a file, we need to parse all the other .gd files present in the directory for class_name calls. Or maybe this is the job of the text editor to track open files with a class_name call.

Try #3199 (https://docs.ctags.io/en/latest/contributions.html#testing-a-pr-locally-before-being-merged).
ctags-#3199 recognizes class_name and gives a proper name to anonClass tag.

I also extract signal names (and its parameters) in #3199.
To know how a signal is used, we have to parse project.godot and tscn files. However, in my understanding, its definition is at .gd file.

@Davidy22
Copy link
Contributor Author

Davidy22 commented Nov 29, 2021

Oh, actually, do we even need to worry about the cross-file thing? The main issue with class_name and signal is that they're only really relevant in other files in the project, but maybe sharing those symbols between files is just the editor's job and collecting the symbols per file is good enough

Geany's ctags looks like it's a little out of date and it's missing a general ctags functions you used, (setTagPositionFromTag not defined, going to copy the definition over) but I'll see how it integrates once I get that sorted

@masatake
Copy link
Member

Oh, actually, do we even need to worry about the cross-file thing? The main issue with class_name and signal is that they're only really relevant in other files in the project, but maybe sharing those symbols between files is just the editor's job and collecting the symbols per file is good enough

At least ctags should extract names newly defined/introduced in a source file.

An editor can ignore the names extracted by ctags.

@Davidy22
Copy link
Contributor Author

Grabbing the class name and signals isn't too tall an order, it's the bit about them needing to be available to other files to provide a complete view of available symbols that I was worried about, but that might just be the editor's job now that I think about it, to aggregate the files it has open

@Davidy22 Davidy22 closed this Nov 29, 2021
masatake added a commit that referenced this pull request Dec 1, 2021
@masatake masatake mentioned this pull request Dec 1, 2021
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants