Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 397 lines (359 sloc) 10.237 kb
568742a Initial restoration.
darren authored
1 /*
2 * $Id$
3 *
212d827 Added email address.
darren authored
4 * Copyright (c) 2000-2001, Thaddeus Covert <sahuagin@mediaone.net>
57f9b9e Integrated improvements by Matthias Veit through Patch #549042.
darren authored
5 * Copyright (c) 2002 Matthias Veit <matthias_veit@yahoo.de>
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
6 * Copyright (c) 2004 Elliott Hughes <enh@acm.org>
568742a Initial restoration.
darren authored
7 *
8 * This source code is released for free distribution under the terms of the
9 * GNU General Public License.
10 *
11 * This module contains functions for generating tags for Ruby language
12 * files.
13 */
14
15 /*
16 * INCLUDE FILES
17 */
18 #include "general.h" /* must always come first */
19
20 #include <string.h>
21
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
22 #include "entry.h"
568742a Initial restoration.
darren authored
23 #include "parse.h"
24 #include "read.h"
25 #include "vstring.h"
26
27 /*
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
28 * DATA DECLARATIONS
568742a Initial restoration.
darren authored
29 */
30 typedef enum {
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
31 K_UNDEFINED = -1, K_CLASS, K_METHOD, K_MODULE, K_SINGLETON
568742a Initial restoration.
darren authored
32 } rubyKind;
33
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
34 /*
35 * DATA DEFINITIONS
36 */
568742a Initial restoration.
darren authored
37 static kindOption RubyKinds [] = {
57f9b9e Integrated improvements by Matthias Veit through Patch #549042.
darren authored
38 { TRUE, 'c', "class", "classes" },
39 { TRUE, 'f', "method", "methods" },
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
40 { TRUE, 'm', "module", "modules" },
41 { TRUE, 'F', "singleton method", "singleton methods" }
568742a Initial restoration.
darren authored
42 };
43
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
44 static stringList* nesting = 0;
568742a Initial restoration.
darren authored
45
46 /*
47 * FUNCTION DEFINITIONS
48 */
49
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
50 /*
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.
57 */
58 static vString* stringListToScope (const stringList* list)
59 {
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)
65 {
66 vString* chunk = stringListItem (list, i);
67 if (vStringLength (chunk) > 0)
68 {
69 vStringCatS (result, (chunks_output++ > 0) ? "." : "");
70 vStringCatS (result, vStringValue (chunk));
71 }
72 }
73 return result;
74 }
75
76 /*
77 * Attempts to advance 's' past 'literal'.
78 * Returns TRUE if it did, FALSE (and leaves 's' where
79 * it was) otherwise.
80 */
81 static boolean canMatch (const unsigned char** s, const char* literal)
82 {
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)
86 {
87 return FALSE;
88 }
89 /* Additionally check that we're at the end of a token. */
90 if ( ! (next_char == 0 || isspace (next_char) || next_char == '('))
91 {
92 return FALSE;
93 }
94 *s += literal_length;
95 return TRUE;
96 }
97
98 /*
99 * Attempts to advance 'cp' past a Ruby operator method name. Returns
100 * TRUE if successful (and copies the name into 'name'), FALSE otherwise.
101 */
102 static boolean parseRubyOperator (vString* name, const unsigned char** cp)
103 {
104 static const char* RUBY_OPERATORS[] = {
105 "[]", "[]=",
106 "**",
107 "!", "~", "+@", "-@",
108 "*", "/", "%",
109 "+", "-",
110 ">>", "<<",
111 "&",
112 "^", "|",
113 "<=", "<", ">", ">=",
114 "<=>", "==", "===", "!=", "=~", "!~",
115 0
116 };
117 int i;
118 for (i = 0; RUBY_OPERATORS[i] != 0; ++i)
119 {
120 if (canMatch (cp, RUBY_OPERATORS[i]))
121 {
122 vStringCatS (name, RUBY_OPERATORS[i]);
123 return TRUE;
124 }
125 }
126 return FALSE;
127 }
128
129 /*
130 * Emits a tag for the given 'name' of kind 'kind' at the current nesting.
131 */
132 static void emitRubyTag (vString* name, rubyKind kind)
133 {
134 tagEntryInfo tag;
135 vString* scope;
136
137 vStringTerminate (name);
138 scope = stringListToScope (nesting);
139
140 initTagEntry (&tag, vStringValue (name));
141 if (vStringLength (scope) > 0) {
142 tag.extensionFields.scope [0] = "class";
143 tag.extensionFields.scope [1] = vStringValue (scope);
144 }
145 tag.kindName = RubyKinds [kind].name;
146 tag.kind = RubyKinds [kind].letter;
147 makeTagEntry (&tag);
148
149 stringListAdd (nesting, vStringNewCopy (name));
150
151 vStringClear (name);
152 vStringDelete (scope);
153 }
154
155 /* Tests whether 'ch' is a character in 'list'. */
156 static boolean charIsIn (char ch, const char* list)
157 {
158 return (strchr (list, ch) != 0);
159 }
160
161 /* Advances 'cp' over leading whitespace. */
162 static void skipWhitespace (const unsigned char** cp)
163 {
164 while (isspace (**cp))
165 {
166 ++*cp;
167 }
168 }
169
170 /*
171 * Copies the characters forming an identifier from *cp into
172 * name, leaving *cp pointing to the character after the identifier.
173 */
174 static rubyKind parseIdentifier (
175 const unsigned char** cp, vString* name, rubyKind kind)
176 {
177 /* Method names are slightly different to class and variable names.
178 * A method name may optionally end with a question mark, exclamation
179 * point or equals sign. These are all part of the name.
180 * A method name may also contain a period if it's a singleton method.
181 */
182 const char* also_ok = (kind == K_METHOD) ? "_.?!=" : "_";
183
184 skipWhitespace (cp);
185
186 /* Check for an anonymous (singleton) class such as "class << HTTP". */
187 if (kind == K_CLASS && **cp == '<' && *(*cp + 1) == '<')
188 {
189 return K_UNDEFINED;
190 }
191
192 /* Check for operators such as "def []=(key, val)". */
193 if (kind == K_METHOD || kind == K_SINGLETON)
194 {
195 if (parseRubyOperator (name, cp))
196 {
197 return kind;
198 }
199 }
200
201 /* Copy the identifier into 'name'. */
202 while (**cp != 0 && (isalnum (**cp) || charIsIn (**cp, also_ok)))
203 {
204 char last_char = **cp;
205
206 vStringPut (name, last_char);
207 ++*cp;
208
209 if (kind == K_METHOD)
210 {
211 /* Recognize singleton methods. */
212 if (last_char == '.')
213 {
214 vStringTerminate (name);
215 vStringClear (name);
216 return parseIdentifier (cp, name, K_SINGLETON);
217 }
218
219 /* Recognize characters which mark the end of a method name. */
220 if (charIsIn (last_char, "?!="))
221 {
222 break;
223 }
224 }
225 }
226 return kind;
227 }
228
229 static void readAndEmitTag (const unsigned char** cp, rubyKind expected_kind)
230 {
231 if (isspace (**cp))
232 {
233 vString *name = vStringNew ();
234 rubyKind actual_kind = parseIdentifier (cp, name, expected_kind);
235
236 if (actual_kind == K_UNDEFINED || vStringLength (name) == 0)
237 {
238 /*
239 * What kind of tags should we create for code like this?
240 *
241 * %w(self.clfloor clfloor).each do |name|
242 * module_eval <<-"end;"
243 * def #{name}(x, y=1)
244 * q, r = x.divmod(y)
245 * q = q.to_i
246 * return q, r
247 * end
248 * end;
249 * end
250 *
251 * Or this?
252 *
253 * class << HTTP
254 *
255 * For now, we don't create any.
256 */
257 }
258 else
259 {
260 emitRubyTag (name, actual_kind);
261 }
262 vStringDelete (name);
263 }
264 }
265
266 static void enterUnnamedScope (void)
267 {
268 stringListAdd (nesting, vStringNewInit (""));
269 }
270
568742a Initial restoration.
darren authored
271 static void findRubyTags (void)
272 {
273 const unsigned char *line;
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
274 boolean inMultiLineComment = FALSE;
275
276 nesting = stringListNew ();
568742a Initial restoration.
darren authored
277
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
278 /* FIXME: this whole scheme is wrong, because Ruby isn't line-based.
279 * You could perfectly well write:
280 *
281 * def
282 * method
283 * puts("hello")
284 * end
285 *
286 * if you wished, and this function would fail to recognize anything.
287 */
568742a Initial restoration.
darren authored
288 while ((line = fileReadLine ()) != NULL)
289 {
57f9b9e Integrated improvements by Matthias Veit through Patch #549042.
darren authored
290 const unsigned char *cp = line;
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
291
292 if (canMatch (&cp, "=begin"))
293 {
294 inMultiLineComment = TRUE;
295 continue;
296 }
297 if (canMatch (&cp, "=end"))
298 {
299 inMultiLineComment = FALSE;
300 continue;
301 }
302
303 skipWhitespace (&cp);
304
305 /* Avoid mistakenly starting a scope for modifiers such as
306 *
307 * return if <exp>
308 *
309 * FIXME: this is fooled by code such as
310 *
311 * result = if <exp>
312 * <a>
313 * else
314 * <b>
315 * end
316 *
317 * FIXME: we're also fooled if someone does something heinous such as
318 *
319 * puts("hello") \
320 * unless <exp>
321 */
322 if (canMatch (&cp, "case") || canMatch (&cp, "for") ||
323 canMatch (&cp, "if") || canMatch (&cp, "unless") ||
324 canMatch (&cp, "while"))
325 {
326 enterUnnamedScope ();
327 }
328
329 /*
330 * "module M", "class C" and "def m" should only be at the beginning
331 * of a line.
332 */
333 if (canMatch (&cp, "module"))
334 {
335 readAndEmitTag (&cp, K_MODULE);
336 }
337 else if (canMatch (&cp, "class"))
338 {
339 readAndEmitTag (&cp, K_CLASS);
340 }
341 else if (canMatch (&cp, "def"))
342 {
343 readAndEmitTag (&cp, K_METHOD);
344 }
345
57f9b9e Integrated improvements by Matthias Veit through Patch #549042.
darren authored
346 while (*cp != '\0')
568742a Initial restoration.
darren authored
347 {
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
348 /* FIXME: we don't cope with here documents, or string literals,
349 * or regular expression literals, or ... you get the idea.
350 * Hopefully, the restriction above that insists on seeing
351 * definitions at the starts of lines should keep us out of
352 * mischief.
353 */
354 if (inMultiLineComment || isspace (*cp))
57f9b9e Integrated improvements by Matthias Veit through Patch #549042.
darren authored
355 {
568742a Initial restoration.
darren authored
356 ++cp;
357 }
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
358 else if (*cp == '#')
568742a Initial restoration.
darren authored
359 {
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
360 /* FIXME: this is wrong, but there *probably* won't be a
361 * definition after an interpolated string (where # doesn't
362 * mean 'comment').
363 */
364 break;
568742a Initial restoration.
darren authored
365 }
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
366 else if (canMatch (&cp, "begin") || canMatch (&cp, "do"))
367 {
368 enterUnnamedScope ();
369 }
370 else if (canMatch (&cp, "end") && stringListCount (nesting) > 0)
371 {
372 /* Leave the most recent scope. */
373 vStringDelete (stringListLast (nesting));
374 stringListRemoveLast (nesting);
375 }
568742a Initial restoration.
darren authored
376 else if (*cp != '\0')
377 {
378 do
379 ++cp;
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
380 while (isalnum (*cp) || *cp == '_');
568742a Initial restoration.
darren authored
381 }
382 }
383 }
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
384 stringListDelete (nesting);
568742a Initial restoration.
darren authored
385 }
386
387 extern parserDefinition* RubyParser (void)
388 {
2935f0f Extended Ruby support with patch from Elliot Hughes on 8 May 2004.
darren authored
389 static const char *const extensions [] = { "rb", "ruby", NULL };
568742a Initial restoration.
darren authored
390 parserDefinition* def = parserNew ("Ruby");
391 def->kinds = RubyKinds;
392 def->kindCount = KIND_COUNT (RubyKinds);
393 def->extensions = extensions;
394 def->parser = findRubyTags;
57f9b9e Integrated improvements by Matthias Veit through Patch #549042.
darren authored
395 return def;
568742a Initial restoration.
darren authored
396 }
Something went wrong with that request. Please try again.