Update for last 2 commits.
[geany-mirror.git] / tagmanager / ruby.c
blob513c5b64630fc705740a76d664dde6d1ffb5c90a
1 /*
2 * $Id$
4 * Copyright (c) 2000-2001, Thaddeus Covert <sahuagin@mediaone.net>
5 * Copyright (c) 2002 Matthias Veit <matthias_veit@yahoo.de>
6 * Copyright (c) 2004 Elliott Hughes <enh@acm.org>
8 * This source code is released for free distribution under the terms of the
9 * GNU General Public License.
11 * This module contains functions for generating tags for Ruby language
12 * files.
16 * INCLUDE FILES
18 #include "general.h" /* must always come first */
20 #include <string.h>
22 #include "entry.h"
23 #include "parse.h"
24 #include "read.h"
25 #include "vstring.h"
28 * DATA DECLARATIONS
30 typedef enum {
31 K_UNDEFINED = -1, K_CLASS, K_METHOD, K_MODULE, K_SINGLETON
32 } rubyKind;
35 * DATA DEFINITIONS
37 static kindOption RubyKinds [] = {
38 { TRUE, 'c', "class", "classes" },
39 { TRUE, 'f', "method", "methods" },
40 { TRUE, 'm', "namespace", "modules" },
41 { TRUE, 'F', "member", "singleton methods" }
44 static stringList* nesting = 0;
47 * FUNCTION DEFINITIONS
51 * Returns a string describing the scope in 'list'.
52 * We record the current scope as a list of entered scopes.
53 * Scopes corresponding to 'if' statements and the like are
54 * represented by empty strings. Scopes corresponding to
55 * modules and classes are represented by the name of the
56 * module or class.
58 static vString* stringListToScope (const stringList* list)
60 unsigned int i;
61 unsigned int chunks_output = 0;
62 vString* result = vStringNew ();
63 const unsigned int max = stringListCount (list);
64 for (i = 0; i < max; ++i)
66 vString* chunk = stringListItem (list, i);
67 if (vStringLength (chunk) > 0)
69 vStringCatS (result, (chunks_output++ > 0) ? "." : "");
70 vStringCatS (result, vStringValue (chunk));
73 return result;
77 * Attempts to advance 's' past 'literal'.
78 * Returns TRUE if it did, FALSE (and leaves 's' where
79 * it was) otherwise.
81 static boolean canMatch (const unsigned char** s, const char* literal)
83 const int literal_length = strlen (literal);
84 const unsigned char next_char = *(*s + literal_length);
85 if (strncmp ((const char*) *s, literal, literal_length) != 0)
87 return FALSE;
89 /* Additionally check that we're at the end of a token. */
90 if ( ! (next_char == 0 || isspace (next_char) || next_char == '('))
92 return FALSE;
94 *s += literal_length;
95 return TRUE;
99 * Attempts to advance 'cp' past a Ruby operator method name. Returns
100 * TRUE if successful (and copies the name into 'name'), FALSE otherwise.
102 static boolean parseRubyOperator (vString* name, const unsigned char** cp)
104 static const char* RUBY_OPERATORS[] = {
105 "[]", "[]=",
106 "**",
107 "!", "~", "+@", "-@",
108 "*", "/", "%",
109 "+", "-",
110 ">>", "<<",
111 "&",
112 "^", "|",
113 "<=", "<", ">", ">=",
114 "<=>", "==", "===", "!=", "=~", "!~",
115 "`",
118 int i;
119 for (i = 0; RUBY_OPERATORS[i] != 0; ++i)
121 if (canMatch (cp, RUBY_OPERATORS[i]))
123 vStringCatS (name, RUBY_OPERATORS[i]);
124 return TRUE;
127 return FALSE;
131 * Emits a tag for the given 'name' of kind 'kind' at the current nesting.
133 static void emitRubyTag (vString* name, rubyKind kind)
135 tagEntryInfo tag;
136 vString* scope;
138 vStringTerminate (name);
139 scope = stringListToScope (nesting);
141 initTagEntry (&tag, vStringValue (name));
142 if (vStringLength (scope) > 0) {
143 tag.extensionFields.scope [0] = "class";
144 tag.extensionFields.scope [1] = vStringValue (scope);
146 tag.kindName = RubyKinds [kind].name;
147 tag.kind = RubyKinds [kind].letter;
148 makeTagEntry (&tag);
150 stringListAdd (nesting, vStringNewCopy (name));
152 vStringClear (name);
153 vStringDelete (scope);
156 /* Tests whether 'ch' is a character in 'list'. */
157 static boolean charIsIn (char ch, const char* list)
159 return (strchr (list, ch) != 0);
162 /* Advances 'cp' over leading whitespace. */
163 static void skipWhitespace (const unsigned char** cp)
165 while (isspace (**cp))
167 ++*cp;
172 * Copies the characters forming an identifier from *cp into
173 * name, leaving *cp pointing to the character after the identifier.
175 static rubyKind parseIdentifier (
176 const unsigned char** cp, vString* name, rubyKind kind)
178 /* Method names are slightly different to class and variable names.
179 * A method name may optionally end with a question mark, exclamation
180 * point or equals sign. These are all part of the name.
181 * A method name may also contain a period if it's a singleton method.
183 const char* also_ok = (kind == K_METHOD) ? "_.?!=" : "_";
185 skipWhitespace (cp);
187 /* Check for an anonymous (singleton) class such as "class << HTTP". */
188 if (kind == K_CLASS && **cp == '<' && *(*cp + 1) == '<')
190 return K_UNDEFINED;
193 /* Check for operators such as "def []=(key, val)". */
194 if (kind == K_METHOD || kind == K_SINGLETON)
196 if (parseRubyOperator (name, cp))
198 return kind;
202 /* Copy the identifier into 'name'. */
203 while (**cp != 0 && (isalnum (**cp) || charIsIn (**cp, also_ok)))
205 char last_char = **cp;
207 vStringPut (name, last_char);
208 ++*cp;
210 if (kind == K_METHOD)
212 /* Recognize singleton methods. */
213 if (last_char == '.')
215 vStringTerminate (name);
216 vStringClear (name);
217 return parseIdentifier (cp, name, K_SINGLETON);
220 /* Recognize characters which mark the end of a method name. */
221 if (charIsIn (last_char, "?!="))
223 break;
227 return kind;
230 static void readAndEmitTag (const unsigned char** cp, rubyKind expected_kind)
232 if (isspace (**cp))
234 vString *name = vStringNew ();
235 rubyKind actual_kind = parseIdentifier (cp, name, expected_kind);
237 if (actual_kind == K_UNDEFINED || vStringLength (name) == 0)
240 * What kind of tags should we create for code like this?
242 * %w(self.clfloor clfloor).each do |name|
243 * module_eval <<-"end;"
244 * def #{name}(x, y=1)
245 * q, r = x.divmod(y)
246 * q = q.to_i
247 * return q, r
248 * end
249 * end;
250 * end
252 * Or this?
254 * class << HTTP
256 * For now, we don't create any.
259 else
261 emitRubyTag (name, actual_kind);
263 vStringDelete (name);
267 static void enterUnnamedScope (void)
269 stringListAdd (nesting, vStringNewInit (""));
272 static void findRubyTags (void)
274 const unsigned char *line;
275 boolean inMultiLineComment = FALSE;
277 nesting = stringListNew ();
279 /* FIXME: this whole scheme is wrong, because Ruby isn't line-based.
280 * You could perfectly well write:
282 * def
283 * method
284 * puts("hello")
285 * end
287 * if you wished, and this function would fail to recognize anything.
289 while ((line = fileReadLine ()) != NULL)
291 const unsigned char *cp = line;
293 if (canMatch (&cp, "=begin"))
295 inMultiLineComment = TRUE;
296 continue;
298 if (canMatch (&cp, "=end"))
300 inMultiLineComment = FALSE;
301 continue;
304 skipWhitespace (&cp);
306 /* Avoid mistakenly starting a scope for modifiers such as
308 * return if <exp>
310 * FIXME: this is fooled by code such as
312 * result = if <exp>
313 * <a>
314 * else
315 * <b>
316 * end
318 * FIXME: we're also fooled if someone does something heinous such as
320 * puts("hello") \
321 * unless <exp>
323 if (canMatch (&cp, "case") || canMatch (&cp, "for") ||
324 canMatch (&cp, "if") || canMatch (&cp, "unless") ||
325 canMatch (&cp, "while"))
327 enterUnnamedScope ();
331 * "module M", "class C" and "def m" should only be at the beginning
332 * of a line.
334 if (canMatch (&cp, "module"))
336 readAndEmitTag (&cp, K_MODULE);
338 else if (canMatch (&cp, "class"))
340 readAndEmitTag (&cp, K_CLASS);
342 else if (canMatch (&cp, "def"))
344 readAndEmitTag (&cp, K_METHOD);
347 while (*cp != '\0')
349 /* FIXME: we don't cope with here documents,
350 * or regular expression literals, or ... you get the idea.
351 * Hopefully, the restriction above that insists on seeing
352 * definitions at the starts of lines should keep us out of
353 * mischief.
355 if (inMultiLineComment || isspace (*cp))
357 ++cp;
359 else if (*cp == '#')
361 /* FIXME: this is wrong, but there *probably* won't be a
362 * definition after an interpolated string (where # doesn't
363 * mean 'comment').
365 break;
367 else if (canMatch (&cp, "begin") || canMatch (&cp, "do"))
369 enterUnnamedScope ();
371 else if (canMatch (&cp, "end") && stringListCount (nesting) > 0)
373 /* Leave the most recent scope. */
374 vStringDelete (stringListLast (nesting));
375 stringListRemoveLast (nesting);
377 else if (*cp == '"')
379 /* Skip string literals.
380 * FIXME: should cope with escapes and interpolation.
382 do {
383 ++cp;
384 } while (*cp != 0 && *cp != '"');
385 if (*cp == '"')
386 cp++; /* skip the last found '"' */
388 else if (*cp != '\0')
391 ++cp;
392 while (isalnum (*cp) || *cp == '_');
396 stringListDelete (nesting);
399 extern parserDefinition* RubyParser (void)
401 static const char *const extensions [] = { "rb", "ruby", NULL };
402 parserDefinition* def = parserNew ("Ruby");
403 def->kinds = RubyKinds;
404 def->kindCount = KIND_COUNT (RubyKinds);
405 def->extensions = extensions;
406 def->parser = findRubyTags;
407 return def;
410 /* vi:set tabstop=4 shiftwidth=4: */