3 * This source code is released for free distribution under the terms of the
4 * GNU General Public License version 2 or (at your option) any later version.
6 * This module contains functions for generating tags for Rust files.
12 #include "general.h" /* must always come first */
28 #define MAX_STRING_LENGTH 256
50 static kindOption rustKinds
[] = {
51 {true, 'n', "module", "module"},
52 {true, 's', "struct", "structural type"},
53 {true, 'i', "interface", "trait interface"},
54 {true, 'c', "implementation", "implementation"},
55 {true, 'f', "function", "Function"},
56 {true, 'g', "enum", "Enum"},
57 {true, 't', "typedef", "Type Alias"},
58 {true, 'v', "variable", "Global variable"},
59 {true, 'M', "macro", "Macro Definition"},
60 {true, 'm', "field", "A struct field"},
61 {true, 'e', "enumerator", "An enum variant"},
62 {true, 'F', "method", "A method"},
91 static void parseBlock (lexerState
*lexer
, bool delim
, int kind
, vString
*scope
);
94 * FUNCTION DEFINITIONS
97 /* Resets the scope string to the old length */
98 static void resetScope (vString
*scope
, size_t old_len
)
100 scope
->length
= old_len
;
101 scope
->buffer
[old_len
] = '\0';
104 /* Adds a name to the end of the scope string */
105 static void addToScope (vString
*scope
, vString
*name
)
107 if (vStringLength(scope
) > 0)
108 vStringCatS(scope
, "::");
109 vStringCat(scope
, name
);
112 /* Write the lexer's current token to string, taking care of special tokens */
113 static void writeCurTokenToStr (lexerState
*lexer
, vString
*out_str
)
115 switch (lexer
->cur_token
)
118 vStringCat(out_str
, lexer
->token_str
);
121 vStringCat(out_str
, lexer
->token_str
);
123 case TOKEN_WHITESPACE
:
124 vStringPut(out_str
, ' ');
127 vStringCatS(out_str
, "<<");
130 vStringCatS(out_str
, ">>");
133 vStringCatS(out_str
, "->");
136 vStringPut(out_str
, (char) lexer
->cur_token
);
140 /* Reads a character from the file */
141 static void advanceChar (lexerState
*lexer
)
143 lexer
->cur_c
= lexer
->next_c
;
144 lexer
->next_c
= getcFromInputFile();
147 /* Reads N characters from the file */
148 static void advanceNChar (lexerState
*lexer
, int n
)
154 /* Store the current character in lexerState::token_str if there is space
155 * (set by MAX_STRING_LENGTH), and then read the next character from the file */
156 static void advanceAndStoreChar (lexerState
*lexer
)
158 if (vStringLength(lexer
->token_str
) < MAX_STRING_LENGTH
)
159 vStringPut(lexer
->token_str
, (char) lexer
->cur_c
);
163 static bool isWhitespace (int c
)
165 return c
== ' ' || c
== '\t' || c
== '\r' || c
== '\n';
168 static bool isAscii (int c
)
170 return (c
>= 0) && (c
< 0x80);
173 /* This isn't quite right for Unicode identifiers */
174 static bool isIdentifierStart (int c
)
176 return (isAscii(c
) && (isalpha(c
) || c
== '_')) || !isAscii(c
);
179 /* This isn't quite right for Unicode identifiers */
180 static bool isIdentifierContinue (int c
)
182 return (isAscii(c
) && (isalnum(c
) || c
== '_')) || !isAscii(c
);
185 static void scanWhitespace (lexerState
*lexer
)
187 while (isWhitespace(lexer
->cur_c
))
191 /* Normal line comments start with two /'s and continue until the next \n
192 * (potentially after a \r). Additionally, a shebang in the beginning of the
193 * file also counts as a line comment as long as it is not this sequence: #![ .
194 * Block comments start with / followed by a * and end with a * followed by a /.
195 * Unlike in C/C++ they nest. */
196 static void scanComments (lexerState
*lexer
)
199 if (lexer
->next_c
== '/')
201 advanceNChar(lexer
, 2);
202 while (lexer
->cur_c
!= EOF
&& lexer
->cur_c
!= '\n')
206 else if (lexer
->next_c
== '!')
208 advanceNChar(lexer
, 2);
209 /* If it is exactly #![ then it is not a comment, but an attribute */
210 if (lexer
->cur_c
== '[')
212 while (lexer
->cur_c
!= EOF
&& lexer
->cur_c
!= '\n')
216 else if (lexer
->next_c
== '*')
219 advanceNChar(lexer
, 2);
220 while (lexer
->cur_c
!= EOF
&& level
> 0)
222 if (lexer
->cur_c
== '*' && lexer
->next_c
== '/')
225 advanceNChar(lexer
, 2);
227 else if (lexer
->cur_c
== '/' && lexer
->next_c
== '*')
230 advanceNChar(lexer
, 2);
240 static void scanIdentifier (lexerState
*lexer
)
242 vStringClear(lexer
->token_str
);
245 advanceAndStoreChar(lexer
);
246 } while(lexer
->cur_c
!= EOF
&& isIdentifierContinue(lexer
->cur_c
));
249 /* Double-quoted strings, we only care about the \" escape. These
250 * last past the end of the line, so be careful not too store too much
251 * of them (see MAX_STRING_LENGTH). The only place we look at their
252 * contents is in the function definitions, and there the valid strings are
253 * things like "C" and "Rust" */
254 static void scanString (lexerState
*lexer
)
256 vStringClear(lexer
->token_str
);
257 advanceAndStoreChar(lexer
);
258 while (lexer
->cur_c
!= EOF
&& lexer
->cur_c
!= '"')
260 if (lexer
->cur_c
== '\\' && lexer
->next_c
== '"')
261 advanceAndStoreChar(lexer
);
262 advanceAndStoreChar(lexer
);
264 advanceAndStoreChar(lexer
);
267 /* Raw strings look like this: r"" or r##""## where the number of
268 * hashes must match */
269 static void scanRawString (lexerState
*lexer
)
271 size_t num_initial_hashes
= 0;
272 vStringClear(lexer
->token_str
);
273 advanceAndStoreChar(lexer
);
274 /* Count how many leading hashes there are */
275 while (lexer
->cur_c
== '#')
277 num_initial_hashes
++;
278 advanceAndStoreChar(lexer
);
280 if (lexer
->cur_c
!= '"')
282 advanceAndStoreChar(lexer
);
283 while (lexer
->cur_c
!= EOF
)
285 /* Count how many trailing hashes there are. If the number is equal or more
286 * than the number of leading hashes, break. */
287 if (lexer
->cur_c
== '"')
289 size_t num_trailing_hashes
= 0;
290 advanceAndStoreChar(lexer
);
291 while (lexer
->cur_c
== '#' && num_trailing_hashes
< num_initial_hashes
)
293 num_trailing_hashes
++;
295 advanceAndStoreChar(lexer
);
297 if (num_trailing_hashes
== num_initial_hashes
)
302 advanceAndStoreChar(lexer
);
307 /* This deals with character literals: 'n', '\n', '\uFFFF'; and lifetimes:
308 * 'lifetime. We'll use this approximate regexp for the literals:
309 * \' \\ [^']+ \' or \' [^'] \' or \' \\ \' \'. Either way, we'll treat this
310 * token as a string, so it gets preserved as is for function signatures with
312 static void scanCharacterOrLifetime (lexerState
*lexer
)
314 vStringClear(lexer
->token_str
);
315 advanceAndStoreChar(lexer
);
317 if (lexer
->cur_c
== '\\')
319 advanceAndStoreChar(lexer
);
320 /* The \' \\ \' \' (literally '\'') case */
321 if (lexer
->cur_c
== '\'' && lexer
->next_c
== '\'')
323 advanceAndStoreChar(lexer
);
324 advanceAndStoreChar(lexer
);
326 /* The \' \\ [^']+ \' case */
329 while (lexer
->cur_c
!= EOF
&& lexer
->cur_c
!= '\'')
330 advanceAndStoreChar(lexer
);
333 /* The \' [^'] \' case */
334 else if (lexer
->cur_c
!= '\'' && lexer
->next_c
== '\'')
336 advanceAndStoreChar(lexer
);
337 advanceAndStoreChar(lexer
);
339 /* Otherwise it is malformed, or a lifetime */
342 /* Advances the parser one token, optionally skipping whitespace
343 * (otherwise it is concatenated and returned as a single whitespace token).
344 * Whitespace is needed to properly render function signatures. Unrecognized
345 * token starts are stored literally, e.g. token may equal to a character '#'. */
346 static int advanceToken (lexerState
*lexer
, bool skip_whitspace
)
348 bool have_whitespace
= false;
349 lexer
->line
= getInputLineNumber();
350 lexer
->pos
= getInputFilePosition();
351 while (lexer
->cur_c
!= EOF
)
353 if (isWhitespace(lexer
->cur_c
))
355 scanWhitespace(lexer
);
356 have_whitespace
= true;
358 else if (lexer
->cur_c
== '/' && (lexer
->next_c
== '/' || lexer
->next_c
== '*'))
361 have_whitespace
= true;
365 if (have_whitespace
&& !skip_whitspace
)
366 return lexer
->cur_token
= TOKEN_WHITESPACE
;
370 lexer
->line
= getInputLineNumber();
371 lexer
->pos
= getInputFilePosition();
372 while (lexer
->cur_c
!= EOF
)
374 if (lexer
->cur_c
== '"')
377 return lexer
->cur_token
= TOKEN_STRING
;
379 else if (lexer
->cur_c
== 'r' && (lexer
->next_c
== '#' || lexer
->next_c
== '"'))
381 scanRawString(lexer
);
382 return lexer
->cur_token
= TOKEN_STRING
;
384 else if (lexer
->cur_c
== '\'')
386 scanCharacterOrLifetime(lexer
);
387 return lexer
->cur_token
= TOKEN_STRING
;
389 else if (isIdentifierStart(lexer
->cur_c
))
391 scanIdentifier(lexer
);
392 return lexer
->cur_token
= TOKEN_IDENT
;
394 /* These shift tokens aren't too important for tag-generation per se,
395 * but they confuse the skipUntil code which tracks the <> pairs. */
396 else if (lexer
->cur_c
== '>' && lexer
->next_c
== '>')
398 advanceNChar(lexer
, 2);
399 return lexer
->cur_token
= TOKEN_RSHIFT
;
401 else if (lexer
->cur_c
== '<' && lexer
->next_c
== '<')
403 advanceNChar(lexer
, 2);
404 return lexer
->cur_token
= TOKEN_LSHIFT
;
406 else if (lexer
->cur_c
== '-' && lexer
->next_c
== '>')
408 advanceNChar(lexer
, 2);
409 return lexer
->cur_token
= TOKEN_RARROW
;
413 int c
= lexer
->cur_c
;
415 return lexer
->cur_token
= c
;
418 return lexer
->cur_token
= TOKEN_EOF
;
421 static void initLexer (lexerState
*lexer
)
423 advanceNChar(lexer
, 2);
424 lexer
->token_str
= vStringNew();
426 if (lexer
->cur_c
== '#' && lexer
->next_c
== '!')
428 advanceToken(lexer
, true);
431 static void deInitLexer (lexerState
*lexer
)
433 vStringDelete(lexer
->token_str
);
434 lexer
->token_str
= NULL
;
437 static void addTag (vString
* ident
, const char* type
, const char* arg_list
, int kind
, unsigned long line
, MIOPos pos
, vString
*scope
, int parent_kind
)
442 initTagEntry(&tag
, ident
->buffer
, &(rustKinds
[kind
]));
444 tag
.lineNumber
= line
;
445 tag
.filePosition
= pos
;
446 tag
.sourceFileName
= getInputFileName();
448 tag
.extensionFields
.signature
= arg_list
;
449 tag
.extensionFields
.varType
= type
;
450 if (parent_kind
!= K_NONE
)
452 tag
.extensionFields
.scopeKind
= &(rustKinds
[parent_kind
]);
453 tag
.extensionFields
.scopeName
= scope
->buffer
;
458 /* Skip tokens until one of the goal tokens is hit. Escapes when level = 0 if there are no goal tokens.
459 * Keeps track of balanced <>'s, ()'s, []'s, and {}'s and ignores the goal tokens within those pairings */
460 static void skipUntil (lexerState
*lexer
, int goal_tokens
[], int num_goal_tokens
)
465 int bracket_level
= 0;
466 while (lexer
->cur_token
!= TOKEN_EOF
)
468 if (angle_level
== 0 && paren_level
== 0 && brace_level
== 0
469 && bracket_level
== 0)
472 for(ii
= 0; ii
< num_goal_tokens
; ii
++)
474 if (lexer
->cur_token
== goal_tokens
[ii
])
479 if (ii
< num_goal_tokens
)
482 switch (lexer
->cur_token
)
509 if (angle_level
>= 2)
512 /* TOKEN_LSHIFT is never interpreted as two <'s in valid Rust code */
516 /* Has to be after the token switch to catch the case when we start with the initial level token */
517 if (num_goal_tokens
== 0 && angle_level
== 0 && paren_level
== 0 && brace_level
== 0
518 && bracket_level
== 0)
520 advanceToken(lexer
, true);
525 * "fn" <ident>[<type_bounds>] "(" [<args>] ")" ["->" <ret_type>] "{" [<body>] "}"*/
526 static void parseFn (lexerState
*lexer
, vString
*scope
, int parent_kind
)
528 int kind
= (parent_kind
== K_TRAIT
|| parent_kind
== K_IMPL
) ? K_METHOD
: K_FN
;
534 bool found_paren
= false;
535 bool valid_signature
= true;
537 advanceToken(lexer
, true);
538 if (lexer
->cur_token
!= TOKEN_IDENT
)
541 name
= vStringNewCopy(lexer
->token_str
);
542 arg_list
= vStringNew();
547 advanceToken(lexer
, true);
549 /* HACK: This is a bit coarse as far as what tag entry means by
551 while (lexer
->cur_token
!= '{' && lexer
->cur_token
!= ';')
553 if (lexer
->cur_token
== '}')
555 valid_signature
= false;
558 else if (lexer
->cur_token
== '(')
563 else if (lexer
->cur_token
== ')')
568 valid_signature
= false;
572 else if (lexer
->cur_token
== TOKEN_EOF
)
574 valid_signature
= false;
577 writeCurTokenToStr(lexer
, arg_list
);
578 advanceToken(lexer
, false);
580 if (!found_paren
|| paren_level
!= 0)
581 valid_signature
= false;
585 vStringStripTrailing(arg_list
);
586 addTag(name
, NULL
, arg_list
->buffer
, kind
, line
, pos
, scope
, parent_kind
);
587 addToScope(scope
, name
);
588 parseBlock(lexer
, true, kind
, scope
);
592 vStringDelete(arg_list
);
596 * "mod" <ident> "{" [<body>] "}"
597 * "mod" <ident> ";"*/
598 static void parseMod (lexerState
*lexer
, vString
*scope
, int parent_kind
)
600 advanceToken(lexer
, true);
601 if (lexer
->cur_token
!= TOKEN_IDENT
)
604 addTag(lexer
->token_str
, NULL
, NULL
, K_MOD
, lexer
->line
, lexer
->pos
, scope
, parent_kind
);
605 addToScope(scope
, lexer
->token_str
);
607 advanceToken(lexer
, true);
609 parseBlock(lexer
, true, K_MOD
, scope
);
613 * "trait" <ident> [<type_bounds>] "{" [<body>] "}"
615 static void parseTrait (lexerState
*lexer
, vString
*scope
, int parent_kind
)
617 int goal_tokens
[] = {'{'};
619 advanceToken(lexer
, true);
620 if (lexer
->cur_token
!= TOKEN_IDENT
)
623 addTag(lexer
->token_str
, NULL
, NULL
, K_TRAIT
, lexer
->line
, lexer
->pos
, scope
, parent_kind
);
624 addToScope(scope
, lexer
->token_str
);
626 advanceToken(lexer
, true);
628 skipUntil(lexer
, goal_tokens
, 1);
630 parseBlock(lexer
, true, K_TRAIT
, scope
);
633 /* Skips type blocks of the form <T:T<T>, ...> */
634 static void skipTypeBlock (lexerState
*lexer
)
636 if (lexer
->cur_token
== '<')
638 skipUntil(lexer
, NULL
, 0);
639 advanceToken(lexer
, true);
643 /* Essentially grabs the last ident before 'for', '<' and '{', which
644 * tends to correspond to what we want as the impl tag entry name */
645 static void parseQualifiedType (lexerState
*lexer
, vString
* name
)
647 while (lexer
->cur_token
!= TOKEN_EOF
)
649 if (lexer
->cur_token
== TOKEN_IDENT
)
651 if (strcmp(lexer
->token_str
->buffer
, "for") == 0
652 || strcmp(lexer
->token_str
->buffer
, "where") == 0)
655 vStringCat(name
, lexer
->token_str
);
657 else if (lexer
->cur_token
== '<' || lexer
->cur_token
== '{')
661 advanceToken(lexer
, true);
663 skipTypeBlock(lexer
);
667 * "impl" [<type_bounds>] <qualified_ident>[<type_bounds>] ["for" <qualified_ident>[<type_bounds>]] "{" [<body>] "}"
669 static void parseImpl (lexerState
*lexer
, vString
*scope
, int parent_kind
)
675 advanceToken(lexer
, true);
680 skipTypeBlock(lexer
);
684 parseQualifiedType(lexer
, name
);
686 if (lexer
->cur_token
== TOKEN_IDENT
&& strcmp(lexer
->token_str
->buffer
, "for") == 0)
688 advanceToken(lexer
, true);
689 parseQualifiedType(lexer
, name
);
692 addTag(name
, NULL
, NULL
, K_IMPL
, line
, pos
, scope
, parent_kind
);
693 addToScope(scope
, name
);
695 parseBlock(lexer
, true, K_IMPL
, scope
);
701 * "static" ["mut"] <ident>
703 static void parseStatic (lexerState
*lexer
, vString
*scope
, int parent_kind
)
705 advanceToken(lexer
, true);
706 if (lexer
->cur_token
!= TOKEN_IDENT
)
708 if (strcmp(lexer
->token_str
->buffer
, "mut") == 0)
710 advanceToken(lexer
, true);
712 if (lexer
->cur_token
!= TOKEN_IDENT
)
715 addTag(lexer
->token_str
, NULL
, NULL
, K_STATIC
, lexer
->line
, lexer
->pos
, scope
, parent_kind
);
721 static void parseType (lexerState
*lexer
, vString
*scope
, int parent_kind
)
723 advanceToken(lexer
, true);
724 if (lexer
->cur_token
!= TOKEN_IDENT
)
727 addTag(lexer
->token_str
, NULL
, NULL
, K_TYPE
, lexer
->line
, lexer
->pos
, scope
, parent_kind
);
730 /* Structs and enums are very similar syntax-wise.
731 * It is possible to parse variants a bit more cleverly (e.g. make tuple variants functions and
732 * struct variants structs) but it'd be too clever and the signature wouldn't make too much sense without
733 * the enum's definition (e.g. for the type bounds)
735 * Struct/Enum format:
736 * "struct/enum" <ident>[<type_bounds>] "{" [<ident>,]+ "}"
737 * "struct/enum" <ident>[<type_bounds>] ";"
739 static void parseStructOrEnum (lexerState
*lexer
, vString
*scope
, int parent_kind
, bool is_struct
)
741 int kind
= is_struct
? K_STRUCT
: K_ENUM
;
742 int field_kind
= is_struct
? K_FIELD
: K_VARIANT
;
743 int goal_tokens1
[] = {';', '{'};
745 advanceToken(lexer
, true);
746 if (lexer
->cur_token
!= TOKEN_IDENT
)
749 addTag(lexer
->token_str
, NULL
, NULL
, kind
, lexer
->line
, lexer
->pos
, scope
, parent_kind
);
750 addToScope(scope
, lexer
->token_str
);
752 skipUntil(lexer
, goal_tokens1
, 2);
754 if (lexer
->cur_token
== '{')
756 vString
*field_name
= vStringNew();
757 while (lexer
->cur_token
!= TOKEN_EOF
)
759 int goal_tokens2
[] = {'}', ','};
760 /* Skip attributes. Format:
763 if (lexer
->cur_token
== '#')
765 advanceToken(lexer
, true);
766 if (lexer
->cur_token
== '!')
767 advanceToken(lexer
, true);
768 if (lexer
->cur_token
== '[')
770 /* It's an attribute, skip it. */
771 skipUntil(lexer
, NULL
, 0);
775 /* Something's up with this field, skip to the next one */
776 skipUntil(lexer
, goal_tokens2
, 2);
780 if (lexer
->cur_token
== TOKEN_IDENT
)
782 if (strcmp(lexer
->token_str
->buffer
, "priv") == 0
783 || strcmp(lexer
->token_str
->buffer
, "pub") == 0)
785 advanceToken(lexer
, true);
786 if (lexer
->cur_token
!= TOKEN_IDENT
)
788 /* Something's up with this field, skip to the next one */
789 skipUntil(lexer
, goal_tokens2
, 2);
794 vStringClear(field_name
);
795 vStringCat(field_name
, lexer
->token_str
);
796 addTag(field_name
, NULL
, NULL
, field_kind
, lexer
->line
, lexer
->pos
, scope
, kind
);
797 skipUntil(lexer
, goal_tokens2
, 2);
799 if (lexer
->cur_token
== '}')
801 advanceToken(lexer
, true);
804 advanceToken(lexer
, true);
806 vStringDelete(field_name
);
810 /* Skip the body of the macro. Can't use skipUntil here as
811 * the body of the macro may have arbitrary code which confuses it (e.g.
812 * bitshift operators/function return arrows) */
813 static void skipMacro (lexerState
*lexer
)
819 advanceToken(lexer
, true);
820 switch (lexer
->cur_token
)
838 while (lexer
->cur_token
!= TOKEN_EOF
)
840 if (lexer
->cur_token
== plus_token
)
842 else if (lexer
->cur_token
== minus_token
)
846 advanceToken(lexer
, true);
848 advanceToken(lexer
, true);
852 * Macro rules format:
853 * "macro_rules" "!" <ident> <macro_body>
855 static void parseMacroRules (lexerState
*lexer
, vString
*scope
, int parent_kind
)
857 advanceToken(lexer
, true);
859 if (lexer
->cur_token
!= '!')
862 advanceToken(lexer
, true);
864 if (lexer
->cur_token
!= TOKEN_IDENT
)
867 addTag(lexer
->token_str
, NULL
, NULL
, K_MACRO
, lexer
->line
, lexer
->pos
, scope
, parent_kind
);
873 * Rust is very liberal with nesting, so this function is used pretty much for any block
875 static void parseBlock (lexerState
*lexer
, bool delim
, int kind
, vString
*scope
)
880 if (lexer
->cur_token
!= '{')
882 advanceToken(lexer
, true);
884 while (lexer
->cur_token
!= TOKEN_EOF
)
886 if (lexer
->cur_token
== TOKEN_IDENT
)
888 size_t old_scope_len
= vStringLength(scope
);
889 if (strcmp(lexer
->token_str
->buffer
, "fn") == 0)
891 parseFn(lexer
, scope
, kind
);
893 else if(strcmp(lexer
->token_str
->buffer
, "mod") == 0)
895 parseMod(lexer
, scope
, kind
);
897 else if(strcmp(lexer
->token_str
->buffer
, "static") == 0)
899 parseStatic(lexer
, scope
, kind
);
901 else if(strcmp(lexer
->token_str
->buffer
, "trait") == 0)
903 parseTrait(lexer
, scope
, kind
);
905 else if(strcmp(lexer
->token_str
->buffer
, "type") == 0)
907 parseType(lexer
, scope
, kind
);
909 else if(strcmp(lexer
->token_str
->buffer
, "impl") == 0)
911 parseImpl(lexer
, scope
, kind
);
913 else if(strcmp(lexer
->token_str
->buffer
, "struct") == 0)
915 parseStructOrEnum(lexer
, scope
, kind
, true);
917 else if(strcmp(lexer
->token_str
->buffer
, "enum") == 0)
919 parseStructOrEnum(lexer
, scope
, kind
, false);
921 else if(strcmp(lexer
->token_str
->buffer
, "macro_rules") == 0)
923 parseMacroRules(lexer
, scope
, kind
);
927 advanceToken(lexer
, true);
928 if (lexer
->cur_token
== '!')
933 resetScope(scope
, old_scope_len
);
935 else if (lexer
->cur_token
== '{')
938 advanceToken(lexer
, true);
940 else if (lexer
->cur_token
== '}')
943 advanceToken(lexer
, true);
945 else if (lexer
->cur_token
== '\'')
947 /* Skip over the 'static lifetime, as it confuses the static parser above */
948 advanceToken(lexer
, true);
949 if (lexer
->cur_token
== TOKEN_IDENT
&& strcmp(lexer
->token_str
->buffer
, "static") == 0)
950 advanceToken(lexer
, true);
954 advanceToken(lexer
, true);
956 if (delim
&& level
<= 0)
961 static void findRustTags (void)
964 vString
* scope
= vStringNew();
967 parseBlock(&lexer
, false, K_NONE
, scope
);
968 vStringDelete(scope
);
973 extern parserDefinition
*RustParser (void)
975 static const char *const extensions
[] = { "rs", NULL
};
976 parserDefinition
*def
= parserNewFull ("Rust", KIND_FILE_ALT
);
977 def
->kinds
= rustKinds
;
978 def
->kindCount
= ARRAY_SIZE (rustKinds
);
979 def
->extensions
= extensions
;
980 def
->parser
= findRustTags
;