Branch: refs/heads/master Author: Colomban Wendling ban@herbesfolles.org Committer: Colomban Wendling ban@herbesfolles.org Date: Sat, 19 Mar 2016 22:22:38 UTC Commit: 83c2a0de696736f0abf11c92d41bad1cc37ac1ff https://github.com/geany/geany/commit/83c2a0de696736f0abf11c92d41bad1cc37ac1...
Log Message: ----------- Merge branch 'ruby/uctags-update'
Update Ruby parser from Universal-CTags.
Closes #587.
Modified Paths: -------------- tagmanager/ctags/ruby.c tests/ctags/Makefile.am tests/ctags/ruby-block-call.rb tests/ctags/ruby-block-call.rb.tags tests/ctags/ruby-class-method-in-lt-lt-self.rb tests/ctags/ruby-class-method-in-lt-lt-self.rb.tags tests/ctags/ruby-doc.rb tests/ctags/ruby-doc.rb.tags tests/ctags/ruby-namespaced-class.rb tests/ctags/ruby-namespaced-class.rb.tags tests/ctags/ruby-scope-after-anonymous-class.rb tests/ctags/ruby-scope-after-anonymous-class.rb.tags tests/ctags/ruby-sf-bug-364.rb tests/ctags/ruby-sf-bug-364.rb.tags
Modified: tagmanager/ctags/ruby.c 214 lines changed, 171 insertions(+), 43 deletions(-) =================================================================== @@ -4,7 +4,7 @@ * Copyright (c) 2004 Elliott Hughes enh@acm.org * * This source code is released for free distribution under the terms of the -* GNU General Public License. +* GNU General Public License version 2 or (at your option) any later version. * * This module contains functions for generating tags for Ruby language * files. @@ -19,6 +19,7 @@
#include "entry.h" #include "parse.h" +#include "nestlevel.h" #include "read.h" #include "vstring.h"
@@ -26,7 +27,7 @@ * DATA DECLARATIONS */ typedef enum { - K_UNDEFINED = -1, K_CLASS, K_METHOD, K_MODULE, K_SINGLETON + K_UNDEFINED = -1, K_CLASS, K_METHOD, K_MODULE, K_SINGLETON, } rubyKind;
/* @@ -36,35 +37,44 @@ static kindOption RubyKinds [] = { { TRUE, 'c', "class", "classes" }, { TRUE, 'f', "method", "methods" }, { TRUE, 'm', "namespace", "modules" }, - { TRUE, 'F', "member", "singleton methods" } + { TRUE, 'F', "member", "singleton methods" }, +#if 0 + /* Following two kinds are reserved. */ + { TRUE, 'd', "describe", "describes and contexts for Rspec" }, + { TRUE, 'C', "constant", "constants" }, +#endif };
-static stringList* nesting = 0; +static NestingLevels* nesting = NULL; + +#define SCOPE_SEPARATOR '.'
/* * FUNCTION DEFINITIONS */
+static void enterUnnamedScope (void); + /* -* Returns a string describing the scope in 'list'. +* Returns a string describing the scope in 'nls'. * We record the current scope as a list of entered scopes. * Scopes corresponding to 'if' statements and the like are * represented by empty strings. Scopes corresponding to * modules and classes are represented by the name of the * module or class. */ -static vString* stringListToScope (const stringList* list) +static vString* nestingLevelsToScope (const NestingLevels* nls) { - unsigned int i; + int i; unsigned int chunks_output = 0; vString* result = vStringNew (); - const unsigned int max = stringListCount (list); - for (i = 0; i < max; ++i) + for (i = 0; i < nls->n; ++i) { - vString* chunk = stringListItem (list, i); + const vString* chunk = nls->levels[i].name; if (vStringLength (chunk) > 0) { - vStringCatS (result, (chunks_output++ > 0) ? "." : ""); + if (chunks_output++ > 0) + vStringPut (result, SCOPE_SEPARATOR); vStringCatS (result, vStringValue (chunk)); } } @@ -76,16 +86,22 @@ static vString* stringListToScope (const stringList* list) * Returns TRUE if it did, FALSE (and leaves 's' where * it was) otherwise. */ -static boolean canMatch (const unsigned char** s, const char* literal) +static boolean canMatch (const unsigned char** s, const char* literal, + boolean (*end_check) (int)) { const int literal_length = strlen (literal); + const int s_length = strlen ((const char *)*s); + + if (s_length < literal_length) + return FALSE; + const unsigned char next_char = *(*s + literal_length); if (strncmp ((const char*) *s, literal, literal_length) != 0) { return FALSE; } /* Additionally check that we're at the end of a token. */ - if ( ! (next_char == 0 || isspace (next_char) || next_char == '(' || next_char == ';')) + if (! end_check (next_char)) { return FALSE; } @@ -93,6 +109,36 @@ static boolean canMatch (const unsigned char** s, const char* literal) return TRUE; }
+static boolean isIdentChar (int c) +{ + return (isalnum (c) || c == '_'); +} + +static boolean notIdentChar (int c) +{ + return ! isIdentChar (c); +} + +static boolean notOperatorChar (int c) +{ + return ! (c == '[' || c == ']' || + c == '=' || c == '!' || c == '~' || + c == '+' || c == '-' || + c == '@' || c == '*' || c == '/' || c == '%' || + c == '<' || c == '>' || + c == '&' || c == '^' || c == '|'); +} + +static boolean isWhitespace (int c) +{ + return c == 0 || isspace (c); +} + +static boolean canMatchKeyword (const unsigned char** s, const char* literal) +{ + return canMatch (s, literal, notIdentChar); +} + /* * Attempts to advance 'cp' past a Ruby operator method name. Returns * TRUE if successful (and copies the name into 'name'), FALSE otherwise. @@ -111,12 +157,12 @@ static boolean parseRubyOperator (vString* name, const unsigned char** cp) "<=", "<", ">", ">=", "<=>", "==", "===", "!=", "=~", "!~", "`", - 0 + NULL }; int i; - for (i = 0; RUBY_OPERATORS[i] != 0; ++i) + for (i = 0; RUBY_OPERATORS[i] != NULL; ++i) { - if (canMatch (cp, RUBY_OPERATORS[i])) + if (canMatch (cp, RUBY_OPERATORS[i], notOperatorChar)) { vStringCatS (name, RUBY_OPERATORS[i]); return TRUE; @@ -132,20 +178,52 @@ static void emitRubyTag (vString* name, rubyKind kind) { tagEntryInfo tag; vString* scope; + rubyKind parent_kind = K_UNDEFINED; + NestingLevel *lvl; + const char *unqualified_name; + const char *qualified_name; + + if (!RubyKinds[kind].enabled) { + return; + }
vStringTerminate (name); - scope = stringListToScope (nesting); + scope = nestingLevelsToScope (nesting); + lvl = nestingLevelsGetCurrent (nesting); + if (lvl) + parent_kind = lvl->type; + + qualified_name = vStringValue (name); + unqualified_name = strrchr (qualified_name, SCOPE_SEPARATOR); + if (unqualified_name && unqualified_name[1]) + { + if (unqualified_name > qualified_name) + { + if (vStringLength (scope) > 0) + vStringPut (scope, SCOPE_SEPARATOR); + vStringNCatS (scope, qualified_name, + unqualified_name - qualified_name); + /* assume module parent type for a lack of a better option */ + parent_kind = K_MODULE; + } + unqualified_name++; + } + else + unqualified_name = qualified_name;
- initTagEntry (&tag, vStringValue (name)); + initTagEntry (&tag, unqualified_name); if (vStringLength (scope) > 0) { - tag.extensionFields.scope [0] = "class"; + Assert (0 <= parent_kind && + (size_t) parent_kind < (sizeof RubyKinds / sizeof RubyKinds[0])); + + tag.extensionFields.scope [0] = RubyKinds [parent_kind].name; tag.extensionFields.scope [1] = vStringValue (scope); } tag.kindName = RubyKinds [kind].name; tag.kind = RubyKinds [kind].letter; makeTagEntry (&tag);
- stringListAdd (nesting, vStringNewCopy (name)); + nestingLevelsPush (nesting, name, kind);
vStringClear (name); vStringDelete (scope); @@ -154,7 +232,7 @@ static void emitRubyTag (vString* name, rubyKind kind) /* Tests whether 'ch' is a character in 'list'. */ static boolean charIsIn (char ch, const char* list) { - return (strchr (list, ch) != 0); + return (strchr (list, ch) != NULL); }
/* Advances 'cp' over leading whitespace. */ @@ -178,7 +256,20 @@ static rubyKind parseIdentifier ( * point or equals sign. These are all part of the name. * A method name may also contain a period if it's a singleton method. */ - const char* also_ok = (kind == K_METHOD) ? "_.?!=" : "_"; + boolean had_sep = FALSE; + const char* also_ok; + if (kind == K_METHOD) + { + also_ok = ".?!="; + } + else if (kind == K_SINGLETON) + { + also_ok = "?!="; + } + else + { + also_ok = ""; + }
skipWhitespace (cp);
@@ -198,11 +289,21 @@ static rubyKind parseIdentifier ( }
/* Copy the identifier into 'name'. */ - while (**cp != 0 && (isalnum (**cp) || charIsIn (**cp, also_ok))) + while (**cp != 0 && (**cp == ':' || isIdentChar (**cp) || charIsIn (**cp, also_ok))) { char last_char = **cp;
- vStringPut (name, last_char); + if (last_char == ':') + had_sep = TRUE; + else + { + if (had_sep) + { + vStringPut (name, SCOPE_SEPARATOR); + had_sep = FALSE; + } + vStringPut (name, last_char); + } ++*cp;
if (kind == K_METHOD) @@ -214,7 +315,10 @@ static rubyKind parseIdentifier ( vStringClear (name); return parseIdentifier (cp, name, K_SINGLETON); } + }
+ if (kind == K_METHOD || kind == K_SINGLETON) + { /* Recognize characters which mark the end of a method name. */ if (charIsIn (last_char, "?!=")) { @@ -253,6 +357,7 @@ static void readAndEmitTag (const unsigned char** cp, rubyKind expected_kind) * * For now, we don't create any. */ + enterUnnamedScope (); } else { @@ -264,7 +369,10 @@ static void readAndEmitTag (const unsigned char** cp, rubyKind expected_kind)
static void enterUnnamedScope (void) { - stringListAdd (nesting, vStringNewInit ("")); + vString *name = vStringNewInit (""); + NestingLevel *parent = nestingLevelsGetCurrent (nesting); + nestingLevelsPush (nesting, name, parent ? parent->type : K_UNDEFINED); + vStringDelete (name); }
static void findRubyTags (void) @@ -272,7 +380,7 @@ static void findRubyTags (void) const unsigned char *line; boolean inMultiLineComment = FALSE;
- nesting = stringListNew (); + nesting = nestingLevelsNew ();
/* FIXME: this whole scheme is wrong, because Ruby isn't line-based. * You could perfectly well write: @@ -291,16 +399,18 @@ static void findRubyTags (void) * separators are "do", ";" or newline */ boolean expect_separator = FALSE;
- if (canMatch (&cp, "=begin")) + if (canMatch (&cp, "=begin", isWhitespace)) { inMultiLineComment = TRUE; continue; } - if (canMatch (&cp, "=end")) + if (canMatch (&cp, "=end", isWhitespace)) { inMultiLineComment = FALSE; continue; } + if (inMultiLineComment) + continue;
skipWhitespace (&cp);
@@ -321,14 +431,16 @@ static void findRubyTags (void) * puts("hello") \ * unless <exp> */ - if (canMatch (&cp, "for") || canMatch (&cp, "until") || - canMatch (&cp, "while")) + if (canMatchKeyword (&cp, "for") || + canMatchKeyword (&cp, "until") || + canMatchKeyword (&cp, "while")) { expect_separator = TRUE; enterUnnamedScope (); } - else if (canMatch (&cp, "case") || canMatch (&cp, "if") || - canMatch (&cp, "unless")) + else if (canMatchKeyword (&cp, "case") || + canMatchKeyword (&cp, "if") || + canMatchKeyword (&cp, "unless")) { enterUnnamedScope (); } @@ -337,17 +449,34 @@ static void findRubyTags (void) * "module M", "class C" and "def m" should only be at the beginning * of a line. */ - if (canMatch (&cp, "module")) + if (canMatchKeyword (&cp, "module")) { readAndEmitTag (&cp, K_MODULE); } - else if (canMatch (&cp, "class")) + else if (canMatchKeyword (&cp, "class")) { readAndEmitTag (&cp, K_CLASS); } - else if (canMatch (&cp, "def")) + else if (canMatchKeyword (&cp, "def")) { - readAndEmitTag (&cp, K_METHOD); + rubyKind kind = K_METHOD; + NestingLevel *nl = nestingLevelsGetCurrent (nesting); + + /* if the def is inside an unnamed scope at the class level, assume + * it's from a singleton from a construct like this: + * + * class C + * class << self + * def singleton + * ... + * end + * end + * end + */ + if (nl && nl->type == K_CLASS && vStringLength (nl->name) == 0) + kind = K_SINGLETON; + + readAndEmitTag (&cp, kind); }
while (*cp != '\0') @@ -370,22 +499,21 @@ static void findRubyTags (void) */ break; } - else if (canMatch (&cp, "begin")) + else if (canMatchKeyword (&cp, "begin")) { enterUnnamedScope (); } - else if (canMatch (&cp, "do")) + else if (canMatchKeyword (&cp, "do")) { if (! expect_separator) enterUnnamedScope (); else expect_separator = FALSE; } - else if (canMatch (&cp, "end") && stringListCount (nesting) > 0) + else if (canMatchKeyword (&cp, "end") && nesting->n > 0) { /* Leave the most recent scope. */ - vStringDelete (stringListLast (nesting)); - stringListRemoveLast (nesting); + nestingLevelsPop (nesting); } else if (*cp == '"') { @@ -407,11 +535,11 @@ static void findRubyTags (void) { do ++cp; - while (isalnum (*cp) || *cp == '_'); + while (isIdentChar (*cp)); } } } - stringListDelete (nesting); + nestingLevelsFree (nesting); }
extern parserDefinition* RubyParser (void)
Modified: tests/ctags/Makefile.am 5 lines changed, 5 insertions(+), 0 deletions(-) =================================================================== @@ -255,6 +255,11 @@ test_sources = \ regexp.js \ return-hint.zep \ return-types.go \ + ruby-block-call.rb \ + ruby-doc.rb \ + ruby-namespaced-class.rb \ + ruby-scope-after-anonymous-class.rb \ + ruby-sf-bug-364.rb \ rules.t2t \ sample.t2t \ secondary_fcn_name.js \
Modified: tests/ctags/ruby-block-call.rb 10 lines changed, 10 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,10 @@ + +def plop + [ 1, 2, 3, 4 ].each do |x| + x > 0 + end.all? +end + +def nothing + puts "nothing" +end
Modified: tests/ctags/ruby-block-call.rb.tags 3 lines changed, 3 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,3 @@ +# format=tagmanager +nothing�128�0 +plop�128�0
Modified: tests/ctags/ruby-class-method-in-lt-lt-self.rb 6 lines changed, 6 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,6 @@ +# (Taken from #455 opened by @mislav). +class C + class << self + def foo() end + end +end
Modified: tests/ctags/ruby-class-method-in-lt-lt-self.rb.tags 2 lines changed, 2 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,2 @@ +C input.rb /^class C$/;" c +foo input.rb /^ def foo() end$/;" F class:C
Modified: tests/ctags/ruby-doc.rb 22 lines changed, 22 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,22 @@ +def f0; end + +=begin +def bug1 +end +=end + +def f1; end + +=begin +def bug2 +end +=end + +def f2; end + +=begin def doesntcount end +def bug3 +end +=end def notparsed end + +def f3; end
Modified: tests/ctags/ruby-doc.rb.tags 5 lines changed, 5 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,5 @@ +# format=tagmanager +f0�128�0 +f1�128�0 +f2�128�0 +f3�128�0
Modified: tests/ctags/ruby-namespaced-class.rb 8 lines changed, 8 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,8 @@ +module A + module B + end +end + +class A::B::C; end + +puts A::B::C
Modified: tests/ctags/ruby-namespaced-class.rb.tags 4 lines changed, 4 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,4 @@ +# format=tagmanager +A�256�0 +B�256�A�0 +C�1�A.B�0
Modified: tests/ctags/ruby-scope-after-anonymous-class.rb 10 lines changed, 10 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,10 @@ +class C + class << self + def foo() end + end + + def bar(); end +end + +puts C.foo +puts C.new.bar
Modified: tests/ctags/ruby-scope-after-anonymous-class.rb.tags 4 lines changed, 4 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,4 @@ +# format=tagmanager +C�1�0 +bar�128�C�0 +foo�64�C�0
Modified: tests/ctags/ruby-sf-bug-364.rb 16 lines changed, 16 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,16 @@ +class D + def self.x? + end + def self.y! + end + def self.z=(a) + end + def self._a? + end + def self._b! + end + def self._c=(a) + end +end +D._c=1 +D.z=1
Modified: tests/ctags/ruby-sf-bug-364.rb.tags 8 lines changed, 8 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,8 @@ +# format=tagmanager +D�1�0 +_a?�64�D�0 +_b!�64�D�0 +_c=�64�D�0 +x?�64�D�0 +y!�64�D�0 +z=�64�D�0
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).