Remove unused variable
[geany-mirror.git] / ctags / parsers / ruby.c
blobfce596c04523e01cf392e0f8283faacdd26c51d9
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 version 2 or (at your option) any later version.
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 <ctype.h>
19 #include <string.h>
21 #include "debug.h"
22 #include "entry.h"
23 #include "parse.h"
24 #include "nestlevel.h"
25 #include "read.h"
26 #include "routines.h"
27 #include "strlist.h"
28 #include "vstring.h"
31 * DATA DECLARATIONS
33 typedef enum {
34 K_UNDEFINED = -1,
35 K_CLASS,
36 K_METHOD,
37 K_MODULE,
38 K_SINGLETON,
39 K_CONST,
40 K_ACCESSOR,
41 K_ALIAS,
42 K_LIBRARY,
43 } rubyKind;
45 typedef enum {
46 RUBY_LIBRARY_REQUIRED,
47 RUBY_LIBRARY_REQUIRED_REL,
48 RUBY_LIBRARY_LOADED,
49 } rubyLibraryRole;
52 * DATA DEFINITIONS
55 static roleDefinition RubyLibraryRoles [] = {
56 { true, "required", "loaded by \"require\" method" },
57 { true, "requiredRel", "loaded by \"require_relative\" method" },
58 { true, "loaded", "loaded by \"load\" method" },
61 static kindDefinition RubyKinds [] = {
62 { true, 'c', "class", "classes" },
63 { true, 'f', "method", "methods" },
64 { true, 'm', "module", "modules" },
65 { true, 'S', "singletonMethod", "singleton methods" },
66 { true, 'C', "constant", "constants" },
67 { true, 'A', "accessor", "accessors" },
68 { true, 'a', "alias", "aliases" },
69 { true, 'L', "library", "libraries",
70 .referenceOnly = true, ATTACH_ROLES(RubyLibraryRoles) },
73 typedef enum {
74 F_MIXIN,
75 } rubyField;
77 static fieldDefinition RubyFields[] = {
78 { .name = "mixin",
79 .description = "how the class or module is mixed in (mixin:HOW:MODULE)",
80 .enabled = true },
83 struct blockData {
84 stringList *mixin;
87 static NestingLevels* nesting = NULL;
89 #define SCOPE_SEPARATOR '.'
92 * FUNCTION DEFINITIONS
95 static void enterUnnamedScope (void);
98 * Returns a string describing the scope in 'nls'.
99 * We record the current scope as a list of entered scopes.
100 * Scopes corresponding to 'if' statements and the like are
101 * represented by empty strings. Scopes corresponding to
102 * modules and classes are represented by the name of the
103 * module or class.
105 static vString* nestingLevelsToScope (const NestingLevels* nls)
107 int i;
108 unsigned int chunks_output = 0;
109 vString* result = vStringNew ();
110 for (i = 0; i < nls->n; ++i)
112 NestingLevel *nl = nestingLevelsGetNthFromRoot (nls, i);
113 tagEntryInfo *e = getEntryOfNestingLevel (nl);
114 if (e && strlen (e->name) > 0 && (!e->placeholder))
116 if (chunks_output++ > 0)
117 vStringPut (result, SCOPE_SEPARATOR);
118 vStringCatS (result, e->name);
121 return result;
125 * Attempts to advance 's' past 'literal'.
126 * Returns true if it did, false (and leaves 's' where
127 * it was) otherwise.
129 static bool canMatch (const unsigned char** s, const char* literal,
130 bool (*end_check) (int))
132 const int literal_length = strlen (literal);
133 const int s_length = strlen ((const char *)*s);
135 if (s_length < literal_length)
136 return false;
138 const unsigned char next_char = *(*s + literal_length);
139 if (strncmp ((const char*) *s, literal, literal_length) != 0)
141 return false;
143 /* Additionally check that we're at the end of a token. */
144 if (! end_check (next_char))
146 return false;
148 *s += literal_length;
149 return true;
152 static bool isIdentChar (int c)
154 return (isalnum (c) || c == '_');
157 static bool notIdentChar (int c)
159 return ! isIdentChar (c);
162 static bool isOperatorChar (int c)
164 return (c == '[' || c == ']' ||
165 c == '=' || c == '!' || c == '~' ||
166 c == '+' || c == '-' ||
167 c == '@' || c == '*' || c == '/' || c == '%' ||
168 c == '<' || c == '>' ||
169 c == '&' || c == '^' || c == '|');
172 static bool notOperatorChar (int c)
174 return ! isOperatorChar (c);
177 static bool isSigilChar (int c)
179 return (c == '@' || c == '$');
182 static bool isWhitespace (int c)
184 return c == 0 || isspace (c);
188 * Advance 's' while the passed predicate is true. Returns true if
189 * advanced by at least one position.
191 static bool advanceWhile (const unsigned char** s, bool (*predicate) (int))
193 const unsigned char* original_pos = *s;
195 while (**s != '\0')
197 if (! predicate (**s))
199 return *s != original_pos;
202 (*s)++;
205 return *s != original_pos;
208 static bool canMatchKeyword (const unsigned char** s, const char* literal)
210 return canMatch (s, literal, notIdentChar);
214 * Extends canMatch. Works similarly, but allows assignment to precede
215 * the keyword, as block assignment is a common Ruby idiom.
217 static bool canMatchKeywordWithAssign (const unsigned char** s, const char* literal)
219 const unsigned char* original_pos = *s;
221 if (canMatchKeyword (s, literal))
223 return true;
226 advanceWhile (s, isSigilChar);
228 if (! advanceWhile (s, isIdentChar))
230 *s = original_pos;
231 return false;
234 advanceWhile (s, isWhitespace);
236 if (! (advanceWhile (s, isOperatorChar) && *(*s - 1) == '='))
238 *s = original_pos;
239 return false;
242 advanceWhile (s, isWhitespace);
244 if (canMatchKeyword (s, literal))
246 return true;
249 *s = original_pos;
250 return false;
254 * Attempts to advance 'cp' past a Ruby operator method name. Returns
255 * true if successful (and copies the name into 'name'), false otherwise.
257 static bool parseRubyOperator (vString* name, const unsigned char** cp)
259 static const char* RUBY_OPERATORS[] = {
260 "[]", "[]=",
261 "**",
262 "!", "~", "+@", "-@",
263 "*", "/", "%",
264 "+", "-",
265 ">>", "<<",
266 "&",
267 "^", "|",
268 "<=", "<", ">", ">=",
269 "<=>", "==", "===", "!=", "=~", "!~",
270 "`",
271 NULL
273 int i;
274 for (i = 0; RUBY_OPERATORS[i] != NULL; ++i)
276 if (canMatch (cp, RUBY_OPERATORS[i], notOperatorChar))
278 vStringCatS (name, RUBY_OPERATORS[i]);
279 return true;
282 return false;
286 * Emits a tag for the given 'name' of kind 'kind' at the current nesting.
288 static int emitRubyTagFull (vString* name, rubyKind kind, bool pushLevel, bool clearName)
290 tagEntryInfo tag;
291 vString* scope;
292 tagEntryInfo *parent;
293 rubyKind parent_kind = K_UNDEFINED;
294 NestingLevel *lvl;
295 const char *unqualified_name;
296 const char *qualified_name;
297 int r;
299 if (!RubyKinds[kind].enabled) {
300 return CORK_NIL;
303 scope = nestingLevelsToScope (nesting);
304 lvl = nestingLevelsGetCurrent (nesting);
305 parent = getEntryOfNestingLevel (lvl);
306 if (parent)
307 parent_kind = parent->kindIndex;
309 qualified_name = vStringValue (name);
310 unqualified_name = strrchr (qualified_name, SCOPE_SEPARATOR);
311 if (unqualified_name && unqualified_name[1])
313 if (unqualified_name > qualified_name)
315 if (vStringLength (scope) > 0)
316 vStringPut (scope, SCOPE_SEPARATOR);
317 vStringNCatS (scope, qualified_name,
318 unqualified_name - qualified_name);
319 /* assume module parent type for a lack of a better option */
320 parent_kind = K_MODULE;
322 unqualified_name++;
324 else
325 unqualified_name = qualified_name;
327 initTagEntry (&tag, unqualified_name, kind);
329 /* Don't fill the scope field for a tag entry representing
330 * a global variable. */
331 if (unqualified_name[0] != '$'
332 && vStringLength (scope) > 0) {
333 Assert (0 <= parent_kind &&
334 (size_t) parent_kind < (ARRAY_SIZE (RubyKinds)));
336 tag.extensionFields.scopeKindIndex = parent_kind;
337 tag.extensionFields.scopeName = vStringValue (scope);
339 r = makeTagEntry (&tag);
341 if (pushLevel)
342 nestingLevelsPush (nesting, r);
344 if (clearName)
345 vStringClear (name);
346 vStringDelete (scope);
348 return r;
351 static int emitRubyTag (vString* name, rubyKind kind)
353 return emitRubyTagFull (name, kind, kind != K_CONST, true);
356 /* Tests whether 'ch' is a character in 'list'. */
357 static bool charIsIn (char ch, const char* list)
359 return (strchr (list, ch) != NULL);
362 /* Advances 'cp' over leading whitespace. */
363 static void skipWhitespace (const unsigned char** cp)
365 while (isspace (**cp))
367 ++*cp;
372 * Copies the characters forming an identifier from *cp into
373 * name, leaving *cp pointing to the character after the identifier.
375 static rubyKind parseIdentifier (
376 const unsigned char** cp, vString* name, rubyKind kind)
378 /* Method names are slightly different to class and variable names.
379 * A method name may optionally end with a question mark, exclamation
380 * point or equals sign. These are all part of the name.
381 * A method name may also contain a period if it's a singleton method.
383 bool had_sep = false;
384 const char* also_ok;
385 if (kind == K_METHOD)
387 also_ok = ".?!=";
389 else if (kind == K_SINGLETON)
391 also_ok = "?!=";
393 else
395 also_ok = "";
398 skipWhitespace (cp);
400 /* Check for an anonymous (singleton) class such as "class << HTTP". */
401 if (kind == K_CLASS && **cp == '<' && *(*cp + 1) == '<')
403 return K_UNDEFINED;
406 /* Check for operators such as "def []=(key, val)". */
407 if (kind == K_METHOD || kind == K_SINGLETON)
409 if (parseRubyOperator (name, cp))
411 return kind;
415 /* Copy the identifier into 'name'. */
416 while (**cp != 0 && (**cp == ':' || isIdentChar (**cp) || charIsIn (**cp, also_ok)))
418 char last_char = **cp;
420 if (last_char == ':')
421 had_sep = true;
422 else
424 if (had_sep)
426 vStringPut (name, SCOPE_SEPARATOR);
427 had_sep = false;
429 vStringPut (name, last_char);
431 ++*cp;
433 if (kind == K_METHOD)
435 /* Recognize singleton methods. */
436 if (last_char == '.')
438 vStringClear (name);
439 return parseIdentifier (cp, name, K_SINGLETON);
443 if (kind == K_METHOD || kind == K_SINGLETON)
445 /* Recognize characters which mark the end of a method name. */
446 if (charIsIn (last_char, "?!="))
448 break;
452 return kind;
455 static void parseString (const unsigned char** cp, unsigned char boundary, vString* vstr)
457 while (**cp != 0 && **cp != boundary)
459 if (vstr)
460 vStringPut (vstr, **cp);
461 ++*cp;
464 /* skip the last found '"' */
465 if (**cp == boundary)
466 ++*cp;
469 static void parseSignature (const unsigned char** cp, vString* vstr)
471 int depth = 1;
473 while (1)
475 /* FIXME:
476 * - handle string literals including ( or ), and
477 * - skip comments.
479 while (! (depth == 0 || **cp == '\0'))
481 if (**cp == '(' || **cp == ')')
483 depth += (**cp == '(')? 1: -1;
484 vStringPut (vstr, **cp);
486 else if (**cp == '#')
488 ++*cp;
489 while (**cp != '\0')
490 ++*cp;
491 break;
493 else if (**cp == '\'' || **cp == '"')
495 unsigned char b = **cp;
496 vStringPut (vstr, b);
497 ++*cp;
498 parseString (cp, b, vstr);
499 vStringPut (vstr, b);
500 continue;
502 else if (isspace (vStringLast (vstr)))
504 if (! (isspace (**cp)))
506 if (**cp == ',')
507 vStringChop (vstr);
508 vStringPut (vstr, **cp);
511 else
512 vStringPut (vstr, **cp);
513 ++*cp;
515 if (depth == 0)
516 return;
518 const unsigned char *line = readLineFromInputFile ();
519 if (line == NULL)
520 return;
521 else
522 *cp = line;
526 static int readAndEmitTagFull (const unsigned char** cp, rubyKind expected_kind,
527 bool pushLevel, bool clearName)
529 int r = CORK_NIL;
530 if (isspace (**cp))
532 vString *name = vStringNew ();
533 rubyKind actual_kind = parseIdentifier (cp, name, expected_kind);
535 if (actual_kind == K_UNDEFINED || vStringLength (name) == 0)
538 * What kind of tags should we create for code like this?
540 * %w(self.clfloor clfloor).each do |name|
541 * module_eval <<-"end;"
542 * def #{name}(x, y=1)
543 * q, r = x.divmod(y)
544 * q = q.to_i
545 * return q, r
546 * end
547 * end;
548 * end
550 * Or this?
552 * class << HTTP
554 * For now, we don't create any.
556 enterUnnamedScope ();
558 else
560 r = emitRubyTagFull (name, actual_kind, pushLevel, clearName);
562 vStringDelete (name);
564 return r;
567 static int readAndEmitTag (const unsigned char** cp, rubyKind expected_kind)
569 return readAndEmitTagFull (cp, expected_kind, expected_kind != K_CONST, true);
572 static void readAndStoreMixinSpec (const unsigned char** cp, const char *how_mixin)
575 NestingLevel *nl = NULL;
576 tagEntryInfo *e = NULL;
577 int ownerLevel = 0;
579 for (ownerLevel = 0; ownerLevel < nesting->n; ownerLevel++)
581 nl = nestingLevelsGetNthParent (nesting, ownerLevel);
582 e = nl? getEntryOfNestingLevel (nl): NULL;
584 /* Ignore "if", "unless", "while" ... */
585 if ((nl && (nl->corkIndex == CORK_NIL)) || (e && e->placeholder))
586 continue;
587 break;
590 if (!e)
591 return;
593 if (e->kindIndex == K_SINGLETON)
595 nl = nestingLevelsGetNthParent (nesting,
596 ownerLevel + 1);
597 if (nl == NULL)
598 return;
599 e = getEntryOfNestingLevel (nl);
602 if (!e)
603 return;
605 if (! (e->kindIndex == K_CLASS || e->kindIndex == K_MODULE))
606 return;
608 if (isspace (**cp) || (**cp == '('))
610 if (isspace (**cp))
611 skipWhitespace (cp);
612 if (**cp == '(')
613 ++*cp;
615 vString *spec = vStringNewInit (how_mixin);
616 vStringPut(spec, ':');
618 size_t len = vStringLength (spec);
619 parseIdentifier (cp, spec, K_MODULE);
620 if (len == vStringLength (spec))
622 vStringDelete (spec);
623 return;
626 struct blockData *bdata = nestingLevelGetUserData (nl);
627 if (bdata->mixin == NULL)
628 bdata->mixin = stringListNew ();
629 stringListAdd (bdata->mixin, spec);
633 static void enterUnnamedScope (void)
635 int r = CORK_NIL;
636 NestingLevel *parent = nestingLevelsGetCurrent (nesting);
637 tagEntryInfo *e_parent = getEntryOfNestingLevel (parent);
639 if (e_parent)
641 tagEntryInfo e;
642 initTagEntry (&e, "", e_parent->kindIndex);
643 e.placeholder = 1;
644 r = makeTagEntry (&e);
646 nestingLevelsPush (nesting, r);
649 static void attachMixinField (int corkIndex, stringList *mixinSpec)
651 vString *mixinField = stringListItem (mixinSpec, 0);
652 for (unsigned int i = 1; i < stringListCount (mixinSpec); i++)
654 vStringPut (mixinField, ',');
655 vStringCat (mixinField, stringListItem (mixinSpec, i));
658 attachParserFieldToCorkEntry (corkIndex, RubyFields [F_MIXIN].ftype,
659 vStringValue (mixinField));
662 static void deleteBlockData (NestingLevel *nl)
664 struct blockData *bdata = nestingLevelGetUserData (nl);
666 if (nl->corkIndex != CORK_NIL
667 && bdata->mixin != NULL
668 && stringListCount (bdata->mixin) > 0)
669 attachMixinField (nl->corkIndex, bdata->mixin);
671 tagEntryInfo *e = getEntryInCorkQueue (nl->corkIndex);
672 if (e && !e->placeholder)
673 e->extensionFields.endLine = getInputLineNumber ();
675 if (bdata->mixin)
676 stringListDelete (bdata->mixin);
679 static bool doesLineIncludeConstant (const unsigned char **cp, vString *constant)
681 const unsigned char **p = cp;
683 if (isspace (**p))
684 skipWhitespace (p);
686 if (isupper (**p))
688 while (**p != 0 && isIdentChar (**p))
690 vStringPut (constant, **p);
691 ++*p;
693 if (isspace (**p))
694 skipWhitespace (p);
696 if (**p == '=')
698 *cp = *p;
699 return true;
701 vStringClear (constant);
704 return false;
707 static void emitRubyAccessorTags (vString *a, bool reader, bool writer)
709 if (vStringLength (a) == 0)
710 return;
712 if (reader)
713 emitRubyTagFull (a, K_ACCESSOR, false, !writer);
714 if (writer)
716 vStringPut (a, '=');
717 emitRubyTagFull (a, K_ACCESSOR, false, true);
721 static void readAttrsAndEmitTags (const unsigned char **cp, bool reader, bool writer)
723 vString *a = vStringNew ();
725 skipWhitespace (cp);
726 if (**cp == '(')
727 ++*cp;
729 do {
730 skipWhitespace (cp);
731 if (**cp == ':')
733 ++*cp;
734 if (K_METHOD == parseIdentifier (cp, a, K_METHOD))
736 emitRubyAccessorTags (a, reader, writer);
737 skipWhitespace (cp);
738 if (**cp == ',')
740 ++*cp;
741 continue;
745 else if (**cp == '"' || **cp == '\'')
747 unsigned char b = **cp;
748 ++*cp;
749 parseString (cp, b, a);
751 emitRubyAccessorTags (a, reader, writer);
752 skipWhitespace (cp);
753 if (**cp == ',')
755 ++*cp;
756 continue;
759 break;
760 } while (1);
762 vStringDelete (a);
765 static int readAliasMethodAndEmitTags (const unsigned char **cp)
767 int r = CORK_NIL;
768 vString *a = vStringNew ();
770 skipWhitespace (cp);
771 if (**cp == '(')
772 ++*cp;
774 skipWhitespace (cp);
775 if (**cp == ':')
777 ++*cp;
778 if (K_METHOD != parseIdentifier (cp, a, K_METHOD))
779 vStringClear (a);
781 else if (**cp == '"' || **cp == '\'')
783 unsigned char b = **cp;
784 ++*cp;
785 parseString (cp, b, a);
788 if (vStringLength (a) > 0)
789 r = emitRubyTagFull (a, K_ALIAS, false, false);
791 vStringDelete (a);
792 return r;
795 static int readStringAndEmitTag (const unsigned char **cp, rubyKind kind, int role)
797 int r = CORK_NIL;
798 vString *s = NULL;
800 skipWhitespace (cp);
801 if (**cp == '(')
802 ++*cp;
804 skipWhitespace (cp);
805 if (**cp == '"' || **cp == '\'')
807 unsigned char b = **cp;
808 ++*cp;
809 s = vStringNew ();
810 parseString (cp, b, s);
813 if (s && vStringLength (s) > 0)
814 r = makeSimpleRefTag (s, kind, role);
816 vStringDelete (s);
817 return r;
820 static int readAndEmitDef (const unsigned char **cp)
822 rubyKind kind = K_METHOD;
823 NestingLevel *nl = nestingLevelsGetCurrent (nesting);
824 tagEntryInfo *e_scope = getEntryOfNestingLevel (nl);
826 /* if the def is inside an unnamed scope at the class level, assume
827 * it's from a singleton from a construct like this:
829 * class C
830 * class << self
831 * def singleton
832 * ...
833 * end
834 * end
835 * end
837 if (e_scope && e_scope->kindIndex == K_CLASS && strlen (e_scope->name) == 0)
838 kind = K_SINGLETON;
839 int corkIndex = readAndEmitTag (cp, kind);
840 tagEntryInfo *e = getEntryInCorkQueue (corkIndex);
842 /* Fill signature: field. */
843 if (e)
845 vString *signature = vStringNewInit ("(");
846 skipWhitespace (cp);
847 if (**cp == '(')
849 ++(*cp);
850 parseSignature (cp, signature);
851 if (vStringLast(signature) != ')')
853 vStringDelete (signature);
854 signature = NULL;
857 else
858 vStringPut (signature, ')');
859 e->extensionFields.signature = vStringDeleteUnwrap (signature);
860 signature = NULL;;
861 vStringDelete (signature);
863 return corkIndex;
866 static void findRubyTags (void)
868 const unsigned char *line;
869 bool inMultiLineComment = false;
870 vString *constant = vStringNew ();
872 nesting = nestingLevelsNewFull (sizeof (struct blockData), deleteBlockData);
874 /* FIXME: this whole scheme is wrong, because Ruby isn't line-based.
875 * You could perfectly well write:
877 * def
878 * method
879 * puts("hello")
880 * end
882 * if you wished, and this function would fail to recognize anything.
884 while ((line = readLineFromInputFile ()) != NULL)
886 const unsigned char *cp = line;
887 /* if we expect a separator after a while, for, or until statement
888 * separators are "do", ";" or newline */
889 bool expect_separator = false;
891 if (canMatch (&cp, "=begin", isWhitespace))
893 inMultiLineComment = true;
894 continue;
896 if (canMatch (&cp, "=end", isWhitespace))
898 inMultiLineComment = false;
899 continue;
901 if (inMultiLineComment)
902 continue;
904 skipWhitespace (&cp);
906 /* Avoid mistakenly starting a scope for modifiers such as
908 * return if <exp>
910 * FIXME: we're fooled if someone does something heinous such as
912 * puts("hello") \
913 * unless <exp>
916 if (canMatchKeywordWithAssign (&cp, "for") ||
917 canMatchKeywordWithAssign (&cp, "until") ||
918 canMatchKeywordWithAssign (&cp, "while"))
920 expect_separator = true;
921 enterUnnamedScope ();
923 else if (canMatchKeywordWithAssign (&cp, "case") ||
924 canMatchKeywordWithAssign (&cp, "if") ||
925 canMatchKeywordWithAssign (&cp, "unless"))
927 enterUnnamedScope ();
931 * "module M", "class C" and "def m" should only be at the beginning
932 * of a line.
934 if (canMatchKeywordWithAssign (&cp, "module"))
936 readAndEmitTag (&cp, K_MODULE);
938 else if (canMatchKeywordWithAssign (&cp, "class"))
940 int r = readAndEmitTag (&cp, K_CLASS);
941 tagEntryInfo *e = getEntryInCorkQueue (r);
943 if (e)
945 skipWhitespace (&cp);
946 if (*cp == '<' && *(cp + 1) != '<')
948 cp++;
949 vString *parent = vStringNew ();
950 parseIdentifier (&cp, parent, K_CLASS);
951 if (vStringLength (parent) > 0)
952 e->extensionFields.inheritance = vStringDeleteUnwrap (parent);
953 else
954 vStringDelete (parent);
958 else if (canMatchKeywordWithAssign (&cp, "include"))
960 readAndStoreMixinSpec (&cp, "include");
962 else if (canMatchKeywordWithAssign (&cp, "prepend"))
964 readAndStoreMixinSpec (&cp, "prepend");
966 else if (canMatchKeywordWithAssign (&cp, "extend"))
968 readAndStoreMixinSpec (&cp, "extend");
970 else if (canMatchKeywordWithAssign (&cp, "def"))
972 readAndEmitDef (&cp);
974 else if (canMatchKeywordWithAssign (&cp, "attr_reader"))
976 readAttrsAndEmitTags (&cp, true, false);
978 else if (canMatchKeywordWithAssign (&cp, "attr_writer"))
980 readAttrsAndEmitTags (&cp, false, true);
982 else if (canMatchKeywordWithAssign (&cp, "attr_accessor"))
984 readAttrsAndEmitTags (&cp, true, true);
986 else if (doesLineIncludeConstant (&cp, constant))
988 emitRubyTag (constant, K_CONST);
989 vStringClear (constant);
991 else if (canMatchKeywordWithAssign (&cp, "require"))
993 readStringAndEmitTag (&cp, K_LIBRARY, RUBY_LIBRARY_REQUIRED);
995 else if (canMatchKeywordWithAssign (&cp, "require_relative"))
997 readStringAndEmitTag (&cp, K_LIBRARY, RUBY_LIBRARY_REQUIRED_REL);
999 else if (canMatchKeywordWithAssign (&cp, "load"))
1001 readStringAndEmitTag (&cp, K_LIBRARY, RUBY_LIBRARY_LOADED);
1003 else if (canMatchKeywordWithAssign (&cp, "alias"))
1005 if (!readAndEmitTagFull (&cp, K_ALIAS, false, true)
1006 && (*cp == '$'))
1008 /* Alias for a global variable. */
1009 ++cp;
1010 vString *alias = vStringNew ();
1011 vStringPut (alias, '$');
1012 if (K_METHOD == parseIdentifier (&cp, alias, K_METHOD)
1013 && vStringLength (alias) > 0)
1014 emitRubyTagFull (alias, K_ALIAS, false, false);
1015 vStringDelete (alias);
1018 else if (canMatchKeywordWithAssign (&cp, "alias_method"))
1019 readAliasMethodAndEmitTags (&cp);
1020 else if ((canMatchKeywordWithAssign (&cp, "private")
1021 || canMatchKeywordWithAssign (&cp, "protected")
1022 || canMatchKeywordWithAssign (&cp, "public")
1023 || canMatchKeywordWithAssign (&cp, "private_class_method")
1024 || canMatchKeywordWithAssign (&cp, "public_class_method")))
1026 skipWhitespace (&cp);
1027 if (canMatchKeywordWithAssign (&cp, "def"))
1028 readAndEmitDef (&cp);
1029 /* TODO: store the method for controlling visibility
1030 * to the "access:" field of the tag.*/
1034 while (*cp != '\0')
1036 /* FIXME: we don't cope with here documents,
1037 * or regular expression literals, or ... you get the idea.
1038 * Hopefully, the restriction above that insists on seeing
1039 * definitions at the starts of lines should keep us out of
1040 * mischief.
1042 if (inMultiLineComment || isspace (*cp))
1044 ++cp;
1046 else if (*cp == '#')
1048 /* FIXME: this is wrong, but there *probably* won't be a
1049 * definition after an interpolated string (where # doesn't
1050 * mean 'comment').
1052 break;
1054 else if (canMatchKeyword (&cp, "begin"))
1056 enterUnnamedScope ();
1058 else if (canMatchKeyword (&cp, "do"))
1060 if (! expect_separator)
1061 enterUnnamedScope ();
1062 else
1063 expect_separator = false;
1065 else if (canMatchKeyword (&cp, "end") && nesting->n > 0)
1067 /* Leave the most recent scope. */
1068 nestingLevelsPop (nesting);
1070 else if (*cp == '"' || *cp == '\'')
1072 unsigned char b = *cp;
1073 /* Skip string literals.
1074 * FIXME: should cope with escapes and interpolation.
1076 ++cp;
1077 parseString (&cp, b, NULL);
1079 else if (*cp == ';')
1081 ++cp;
1082 expect_separator = false;
1084 else if (*cp != '\0')
1087 ++cp;
1088 while (isIdentChar (*cp));
1092 nestingLevelsFree (nesting);
1093 vStringDelete (constant);
1096 extern parserDefinition* RubyParser (void)
1098 static const char *const extensions [] = { "rb", "ruby", NULL };
1099 parserDefinition* def = parserNew ("Ruby");
1100 def->kindTable = RubyKinds;
1101 def->kindCount = ARRAY_SIZE (RubyKinds);
1102 def->extensions = extensions;
1103 def->parser = findRubyTags;
1104 def->fieldTable = RubyFields;
1105 def->fieldCount = ARRAY_SIZE (RubyFields);
1106 def->useCork = CORK_QUEUE;
1107 return def;