⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ruby.c

📁 Exuberant Ctags is a multilanguage reimplementation of the much-underused ctags(1) program and is i
💻 C
字号:
/**   $Id: ruby.c,v 1.6 2006/05/30 04:37:12 darren Exp $**   Copyright (c) 2000-2001, Thaddeus Covert <sahuagin@mediaone.net>*   Copyright (c) 2002 Matthias Veit <matthias_veit@yahoo.de>*   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.**   This module contains functions for generating tags for Ruby language*   files.*//**   INCLUDE FILES*/#include "general.h"  /* must always come first */#include <string.h>#include "entry.h"#include "parse.h"#include "read.h"#include "vstring.h"/**   DATA DECLARATIONS*/typedef enum {	K_UNDEFINED = -1, K_CLASS, K_METHOD, K_MODULE, K_SINGLETON} rubyKind;/**   DATA DEFINITIONS*/static kindOption RubyKinds [] = {	{ TRUE, 'c', "class",  "classes" },	{ TRUE, 'f', "method", "methods" },	{ TRUE, 'm', "module", "modules" },	{ TRUE, 'F', "singleton method", "singleton methods" }};static stringList* nesting = 0;/**   FUNCTION DEFINITIONS*//** Returns a string describing the scope in 'list'.* 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){	unsigned int i;	unsigned int chunks_output = 0;	vString* result = vStringNew ();	const unsigned int max = stringListCount (list);	for (i = 0; i < max; ++i)	{	    vString* chunk = stringListItem (list, i);	    if (vStringLength (chunk) > 0)	    {	        vStringCatS (result, (chunks_output++ > 0) ? "." : "");	        vStringCatS (result, vStringValue (chunk));	    }	}	return result;}/** Attempts to advance 's' past 'literal'.* Returns TRUE if it did, FALSE (and leaves 's' where* it was) otherwise.*/static boolean canMatch (const unsigned char** s, const char* literal){	const int literal_length = strlen (literal);	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 == '('))	{	    return FALSE;	}	*s += literal_length;	return TRUE;}/** Attempts to advance 'cp' past a Ruby operator method name. Returns* TRUE if successful (and copies the name into 'name'), FALSE otherwise.*/static boolean parseRubyOperator (vString* name, const unsigned char** cp){	static const char* RUBY_OPERATORS[] = {	    "[]", "[]=",	    "**",	    "!", "~", "+@", "-@",	    "*", "/", "%",	    "+", "-",	    ">>", "<<",	    "&",	    "^", "|",	    "<=", "<", ">", ">=",	    "<=>", "==", "===", "!=", "=~", "!~",	    0	};	int i;	for (i = 0; RUBY_OPERATORS[i] != 0; ++i)	{	    if (canMatch (cp, RUBY_OPERATORS[i]))	    {	        vStringCatS (name, RUBY_OPERATORS[i]);	        return TRUE;	    }	}	return FALSE;}/** Emits a tag for the given 'name' of kind 'kind' at the current nesting.*/static void emitRubyTag (vString* name, rubyKind kind){	tagEntryInfo tag;	vString* scope;	vStringTerminate (name);	scope = stringListToScope (nesting);	initTagEntry (&tag, vStringValue (name));	if (vStringLength (scope) > 0) {	    tag.extensionFields.scope [0] = "class";	    tag.extensionFields.scope [1] = vStringValue (scope);	}	tag.kindName = RubyKinds [kind].name;	tag.kind = RubyKinds [kind].letter;	makeTagEntry (&tag);	stringListAdd (nesting, vStringNewCopy (name));	vStringClear (name);	vStringDelete (scope);}/* Tests whether 'ch' is a character in 'list'. */static boolean charIsIn (char ch, const char* list){	return (strchr (list, ch) != 0);}/* Advances 'cp' over leading whitespace. */static void skipWhitespace (const unsigned char** cp){	while (isspace (**cp))	{	    ++*cp;	}}/** Copies the characters forming an identifier from *cp into* name, leaving *cp pointing to the character after the identifier.*/static rubyKind parseIdentifier (		const unsigned char** cp, vString* name, rubyKind kind){	/* Method names are slightly different to class and variable names.	 * A method name may optionally end with a question mark, exclamation	 * 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) ? "_.?!=" : "_";	skipWhitespace (cp);	/* Check for an anonymous (singleton) class such as "class << HTTP". */	if (kind == K_CLASS && **cp == '<' && *(*cp + 1) == '<')	{		return K_UNDEFINED;	}	/* Check for operators such as "def []=(key, val)". */	if (kind == K_METHOD || kind == K_SINGLETON)	{		if (parseRubyOperator (name, cp))		{			return kind;		}	}	/* Copy the identifier into 'name'. */	while (**cp != 0 && (isalnum (**cp) || charIsIn (**cp, also_ok)))	{		char last_char = **cp;		vStringPut (name, last_char);		++*cp;		if (kind == K_METHOD)		{			/* Recognize singleton methods. */			if (last_char == '.')			{				vStringTerminate (name);				vStringClear (name);				return parseIdentifier (cp, name, K_SINGLETON);			}			/* Recognize characters which mark the end of a method name. */			if (charIsIn (last_char, "?!="))			{				break;			}		}	}	return kind;}static void readAndEmitTag (const unsigned char** cp, rubyKind expected_kind){	if (isspace (**cp))	{		vString *name = vStringNew ();		rubyKind actual_kind = parseIdentifier (cp, name, expected_kind);		if (actual_kind == K_UNDEFINED || vStringLength (name) == 0)		{			/*			* What kind of tags should we create for code like this?			*			*    %w(self.clfloor clfloor).each do |name|			*        module_eval <<-"end;"			*            def #{name}(x, y=1)			*                q, r = x.divmod(y)			*                q = q.to_i			*                return q, r			*            end			*        end;			*    end			*			* Or this?			*			*    class << HTTP			*			* For now, we don't create any.			*/		}		else		{			emitRubyTag (name, actual_kind);		}		vStringDelete (name);	}}static void enterUnnamedScope (void){	stringListAdd (nesting, vStringNewInit (""));}static void findRubyTags (void){	const unsigned char *line;	boolean inMultiLineComment = FALSE;	nesting = stringListNew ();	/* FIXME: this whole scheme is wrong, because Ruby isn't line-based.	* You could perfectly well write:	*	*  def	*  method	*   puts("hello")	*  end	*	* if you wished, and this function would fail to recognize anything.	*/	while ((line = fileReadLine ()) != NULL)	{		const unsigned char *cp = line;		if (canMatch (&cp, "=begin"))		{			inMultiLineComment = TRUE;			continue;		}		if (canMatch (&cp, "=end"))		{			inMultiLineComment = FALSE;			continue;		}		skipWhitespace (&cp);		/* Avoid mistakenly starting a scope for modifiers such as		*		*   return if <exp>		*		* FIXME: this is fooled by code such as		*		*   result = if <exp>		*               <a>		*            else		*               <b>		*            end		*		* FIXME: we're also fooled if someone does something heinous such as		*		*   puts("hello") \		*       unless <exp>		*/		if (canMatch (&cp, "case") || canMatch (&cp, "for") ||			canMatch (&cp, "if") || canMatch (&cp, "unless") ||			canMatch (&cp, "while"))		{			enterUnnamedScope ();		}		/*		* "module M", "class C" and "def m" should only be at the beginning		* of a line.		*/		if (canMatch (&cp, "module"))		{			readAndEmitTag (&cp, K_MODULE);		}		else if (canMatch (&cp, "class"))		{			readAndEmitTag (&cp, K_CLASS);		}		else if (canMatch (&cp, "def"))		{			readAndEmitTag (&cp, K_METHOD);		}		while (*cp != '\0')		{			/* FIXME: we don't cope with here documents, or string literals,			* or regular expression literals, or ... you get the idea.			* Hopefully, the restriction above that insists on seeing			* definitions at the starts of lines should keep us out of			* mischief.			*/			if (inMultiLineComment || isspace (*cp))			{				++cp;			}			else if (*cp == '#')			{				/* FIXME: this is wrong, but there *probably* won't be a				* definition after an interpolated string (where # doesn't				* mean 'comment').				*/				break;			}			else if (canMatch (&cp, "begin") || canMatch (&cp, "do"))			{				enterUnnamedScope ();			}			else if (canMatch (&cp, "end") && stringListCount (nesting) > 0)			{				/* Leave the most recent scope. */				vStringDelete (stringListLast (nesting));			stringListRemoveLast (nesting);			}			else if (*cp != '\0')			{				do					++cp;				while (isalnum (*cp) || *cp == '_');			}		}	}	stringListDelete (nesting);}extern parserDefinition* RubyParser (void){	static const char *const extensions [] = { "rb", "ruby", NULL };	parserDefinition* def = parserNew ("Ruby");	def->kinds      = RubyKinds;	def->kindCount  = KIND_COUNT (RubyKinds);	def->extensions = extensions;	def->parser     = findRubyTags;	return def;}/* vi:set tabstop=4 shiftwidth=4: */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -