Fix building with makefile.win32 from Windows command prompt, not MSYS
[geany-mirror.git] / tagmanager / ruby.c
bloba614eb1e577cff51f36722ef384aae4c54058833
1 /*
2 * Copyright (c) 2000-2001, Thaddeus Covert <sahuagin@mediaone.net>
3 * Copyright (c) 2002 Matthias Veit <matthias_veit@yahoo.de>
4 * Copyright (c) 2004 Elliott Hughes <enh@acm.org>
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License.
9 * This module contains functions for generating tags for Ruby language
10 * files.
14 * INCLUDE FILES
16 #include "general.h" /* must always come first */
18 #include <string.h>
20 #include "entry.h"
21 #include "parse.h"
22 #include "read.h"
23 #include "vstring.h"
26 * DATA DECLARATIONS
28 typedef enum {
29 K_UNDEFINED = -1, K_CLASS, K_METHOD, K_MODULE, K_SINGLETON
30 } rubyKind;
33 * DATA DEFINITIONS
35 static kindOption RubyKinds [] = {
36 { TRUE, 'c', "class", "classes" },
37 { TRUE, 'f', "method", "methods" },
38 { TRUE, 'm', "namespace", "modules" },
39 { TRUE, 'F', "member", "singleton methods" }
42 static stringList* nesting = 0;
45 * FUNCTION DEFINITIONS
49 * Returns a string describing the scope in 'list'.
50 * We record the current scope as a list of entered scopes.
51 * Scopes corresponding to 'if' statements and the like are
52 * represented by empty strings. Scopes corresponding to
53 * modules and classes are represented by the name of the
54 * module or class.
56 static vString* stringListToScope (const stringList* list)
58 unsigned int i;
59 unsigned int chunks_output = 0;
60 vString* result = vStringNew ();
61 const unsigned int max = stringListCount (list);
62 for (i = 0; i < max; ++i)
64 vString* chunk = stringListItem (list, i);
65 if (vStringLength (chunk) > 0)
67 vStringCatS (result, (chunks_output++ > 0) ? "." : "");
68 vStringCatS (result, vStringValue (chunk));
71 return result;
75 * Attempts to advance 's' past 'literal'.
76 * Returns TRUE if it did, FALSE (and leaves 's' where
77 * it was) otherwise.
79 static boolean canMatch (const unsigned char** s, const char* literal)
81 const int literal_length = strlen (literal);
82 const unsigned char next_char = *(*s + literal_length);
83 if (strncmp ((const char*) *s, literal, literal_length) != 0)
85 return FALSE;
87 /* Additionally check that we're at the end of a token. */
88 if ( ! (next_char == 0 || isspace (next_char) || next_char == '('))
90 return FALSE;
92 *s += literal_length;
93 return TRUE;
97 * Attempts to advance 'cp' past a Ruby operator method name. Returns
98 * TRUE if successful (and copies the name into 'name'), FALSE otherwise.
100 static boolean parseRubyOperator (vString* name, const unsigned char** cp)
102 static const char* RUBY_OPERATORS[] = {
103 "[]", "[]=",
104 "**",
105 "!", "~", "+@", "-@",
106 "*", "/", "%",
107 "+", "-",
108 ">>", "<<",
109 "&",
110 "^", "|",
111 "<=", "<", ">", ">=",
112 "<=>", "==", "===", "!=", "=~", "!~",
113 "`",
116 int i;
117 for (i = 0; RUBY_OPERATORS[i] != 0; ++i)
119 if (canMatch (cp, RUBY_OPERATORS[i]))
121 vStringCatS (name, RUBY_OPERATORS[i]);
122 return TRUE;
125 return FALSE;
129 * Emits a tag for the given 'name' of kind 'kind' at the current nesting.
131 static void emitRubyTag (vString* name, rubyKind kind)
133 tagEntryInfo tag;
134 vString* scope;
136 vStringTerminate (name);
137 scope = stringListToScope (nesting);
139 initTagEntry (&tag, vStringValue (name));
140 if (vStringLength (scope) > 0) {
141 tag.extensionFields.scope [0] = "class";
142 tag.extensionFields.scope [1] = vStringValue (scope);
144 tag.kindName = RubyKinds [kind].name;
145 tag.kind = RubyKinds [kind].letter;
146 makeTagEntry (&tag);
148 stringListAdd (nesting, vStringNewCopy (name));
150 vStringClear (name);
151 vStringDelete (scope);
154 /* Tests whether 'ch' is a character in 'list'. */
155 static boolean charIsIn (char ch, const char* list)
157 return (strchr (list, ch) != 0);
160 /* Advances 'cp' over leading whitespace. */
161 static void skipWhitespace (const unsigned char** cp)
163 while (isspace (**cp))
165 ++*cp;
170 * Copies the characters forming an identifier from *cp into
171 * name, leaving *cp pointing to the character after the identifier.
173 static rubyKind parseIdentifier (
174 const unsigned char** cp, vString* name, rubyKind kind)
176 /* Method names are slightly different to class and variable names.
177 * A method name may optionally end with a question mark, exclamation
178 * point or equals sign. These are all part of the name.
179 * A method name may also contain a period if it's a singleton method.
181 const char* also_ok = (kind == K_METHOD) ? "_.?!=" : "_";
183 skipWhitespace (cp);
185 /* Check for an anonymous (singleton) class such as "class << HTTP". */
186 if (kind == K_CLASS && **cp == '<' && *(*cp + 1) == '<')
188 return K_UNDEFINED;
191 /* Check for operators such as "def []=(key, val)". */
192 if (kind == K_METHOD || kind == K_SINGLETON)
194 if (parseRubyOperator (name, cp))
196 return kind;
200 /* Copy the identifier into 'name'. */
201 while (**cp != 0 && (isalnum (**cp) || charIsIn (**cp, also_ok)))
203 char last_char = **cp;
205 vStringPut (name, last_char);
206 ++*cp;
208 if (kind == K_METHOD)
210 /* Recognize singleton methods. */
211 if (last_char == '.')
213 vStringTerminate (name);
214 vStringClear (name);
215 return parseIdentifier (cp, name, K_SINGLETON);
218 /* Recognize characters which mark the end of a method name. */
219 if (charIsIn (last_char, "?!="))
221 break;
225 return kind;
228 static void readAndEmitTag (const unsigned char** cp, rubyKind expected_kind)
230 if (isspace (**cp))
232 vString *name = vStringNew ();
233 rubyKind actual_kind = parseIdentifier (cp, name, expected_kind);
235 if (actual_kind == K_UNDEFINED || vStringLength (name) == 0)
238 * What kind of tags should we create for code like this?
240 * %w(self.clfloor clfloor).each do |name|
241 * module_eval <<-"end;"
242 * def #{name}(x, y=1)
243 * q, r = x.divmod(y)
244 * q = q.to_i
245 * return q, r
246 * end
247 * end;
248 * end
250 * Or this?
252 * class << HTTP
254 * For now, we don't create any.
257 else
259 emitRubyTag (name, actual_kind);
261 vStringDelete (name);
265 static void enterUnnamedScope (void)
267 stringListAdd (nesting, vStringNewInit (""));
270 static void findRubyTags (void)
272 const unsigned char *line;
273 boolean inMultiLineComment = FALSE;
275 nesting = stringListNew ();
277 /* FIXME: this whole scheme is wrong, because Ruby isn't line-based.
278 * You could perfectly well write:
280 * def
281 * method
282 * puts("hello")
283 * end
285 * if you wished, and this function would fail to recognize anything.
287 while ((line = fileReadLine ()) != NULL)
289 const unsigned char *cp = line;
291 if (canMatch (&cp, "=begin"))
293 inMultiLineComment = TRUE;
294 continue;
296 if (canMatch (&cp, "=end"))
298 inMultiLineComment = FALSE;
299 continue;
302 skipWhitespace (&cp);
304 /* Avoid mistakenly starting a scope for modifiers such as
306 * return if <exp>
308 * FIXME: this is fooled by code such as
310 * result = if <exp>
311 * <a>
312 * else
313 * <b>
314 * end
316 * FIXME: we're also fooled if someone does something heinous such as
318 * puts("hello") \
319 * unless <exp>
321 if (canMatch (&cp, "case") || canMatch (&cp, "for") ||
322 canMatch (&cp, "if") || canMatch (&cp, "unless") ||
323 canMatch (&cp, "while"))
325 enterUnnamedScope ();
329 * "module M", "class C" and "def m" should only be at the beginning
330 * of a line.
332 if (canMatch (&cp, "module"))
334 readAndEmitTag (&cp, K_MODULE);
336 else if (canMatch (&cp, "class"))
338 readAndEmitTag (&cp, K_CLASS);
340 else if (canMatch (&cp, "def"))
342 readAndEmitTag (&cp, K_METHOD);
345 while (*cp != '\0')
347 /* FIXME: we don't cope with here documents,
348 * or regular expression literals, or ... you get the idea.
349 * Hopefully, the restriction above that insists on seeing
350 * definitions at the starts of lines should keep us out of
351 * mischief.
353 if (inMultiLineComment || isspace (*cp))
355 ++cp;
357 else if (*cp == '#')
359 /* FIXME: this is wrong, but there *probably* won't be a
360 * definition after an interpolated string (where # doesn't
361 * mean 'comment').
363 break;
365 else if (canMatch (&cp, "begin") || canMatch (&cp, "do"))
367 enterUnnamedScope ();
369 else if (canMatch (&cp, "end") && stringListCount (nesting) > 0)
371 /* Leave the most recent scope. */
372 vStringDelete (stringListLast (nesting));
373 stringListRemoveLast (nesting);
375 else if (*cp == '"')
377 /* Skip string literals.
378 * FIXME: should cope with escapes and interpolation.
380 do {
381 ++cp;
382 } while (*cp != 0 && *cp != '"');
383 if (*cp == '"')
384 cp++; /* skip the last found '"' */
386 else if (*cp != '\0')
389 ++cp;
390 while (isalnum (*cp) || *cp == '_');
394 stringListDelete (nesting);
397 extern parserDefinition* RubyParser (void)
399 static const char *const extensions [] = { "rb", "ruby", NULL };
400 parserDefinition* def = parserNew ("Ruby");
401 def->kinds = RubyKinds;
402 def->kindCount = KIND_COUNT (RubyKinds);
403 def->extensions = extensions;
404 def->parser = findRubyTags;
405 return def;
408 /* vi:set tabstop=4 shiftwidth=4: */