[geany/geany] 83c2a0: Merge branch 'ruby/uctags-update'

Colomban Wendling git-noreply at xxxxx
Sat Mar 19 22:22:38 UTC 2016


Branch:      refs/heads/master
Author:      Colomban Wendling <ban at herbesfolles.org>
Committer:   Colomban Wendling <ban at herbesfolles.org>
Date:        Sat, 19 Mar 2016 22:22:38 UTC
Commit:      83c2a0de696736f0abf11c92d41bad1cc37ac1ff
             https://github.com/geany/geany/commit/83c2a0de696736f0abf11c92d41bad1cc37ac1ff

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 at 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).


More information about the Commits mailing list