1 /*
2 * This source code is released for free distribution under the terms of the
3 * GNU General Public License version 2 or (at your option) any later version.
4 *
5 * Reference:
6 * https://golang.org/ref/spec
7 */
8
9
10 /*
11 * INCLUDE FILES
12 */
13 #include "general.h" /* must always come first */
14
15 #include "debug.h"
16 #include "entry.h"
17 #include "keyword.h"
18 #include "read.h"
19 #include "numarray.h"
20 #include "objpool.h"
21 #include "parse.h"
22 #include "routines.h"
23 #include "vstring.h"
24 #include "xtag.h"
25 #include "field.h"
26 #include "htable.h"
27
28 #include <string.h>
29
30 /*
31 * MACROS
32 */
33 #define MAX_COLLECTOR_LENGTH 512
34 #define isType(token,t) (bool) ((token)->type == (t))
35 #define isKeyword(token,k) (bool) ((token)->keyword == (k))
36 #define isStartIdentChar(c) (isalpha (c) || (c) == '_' || (c) > 128) /* XXX UTF-8 */
37 #define isIdentChar(c) (isStartIdentChar (c) || isdigit (c))
38 #define newToken() (objPoolGet (TokenPool))
39 #define deleteToken(t) (objPoolPut (TokenPool, (t)))
40
41 /*
42 * DATA DECLARATIONS
43 */
44
45 enum eKeywordId {
46 KEYWORD_package,
47 KEYWORD_import,
48 KEYWORD_const,
49 KEYWORD_type,
50 KEYWORD_var,
51 KEYWORD_func,
52 KEYWORD_struct,
53 KEYWORD_interface,
54 KEYWORD_map,
55 KEYWORD_chan
56 };
57 typedef int keywordId; /* to allow KEYWORD_NONE */
58
59 typedef enum eTokenType {
60 TOKEN_NONE = -1,
61 // Token not important for top-level Go parsing
62 TOKEN_OTHER,
63 TOKEN_KEYWORD,
64 TOKEN_IDENTIFIER,
65 TOKEN_STRING,
66 TOKEN_OPEN_PAREN,
67 TOKEN_CLOSE_PAREN,
68 TOKEN_OPEN_CURLY,
69 TOKEN_CLOSE_CURLY,
70 TOKEN_OPEN_SQUARE,
71 TOKEN_CLOSE_SQUARE,
72 TOKEN_SEMICOLON,
73 TOKEN_STAR,
74 TOKEN_LEFT_ARROW,
75 TOKEN_DOT,
76 TOKEN_COMMA,
77 TOKEN_EQUAL,
78 TOKEN_3DOTS,
79 TOKEN_EOF
80 } tokenType;
81
82 typedef struct sTokenInfo {
83 tokenType type;
84 keywordId keyword;
85 vString *string; /* the name of the token */
86 unsigned long lineNumber; /* line number of tag */
87 MIOPos filePosition; /* file position of line containing name */
88 int c; /* Used in AppendTokenToVString */
89 } tokenInfo;
90
91 typedef struct sCollector {
92 vString *str;
93 size_t last_len;
94 } collector;
95
96 /*
97 * DATA DEFINITIONS
98 */
99
100 static int Lang_go;
101 static objPool *TokenPool = NULL;
102
103 typedef enum {
104 GOTAG_UNDEFINED = -1,
105 GOTAG_PACKAGE,
106 GOTAG_FUNCTION,
107 GOTAG_CONST,
108 GOTAG_TYPE,
109 GOTAG_VAR,
110 GOTAG_STRUCT,
111 GOTAG_INTERFACE,
112 GOTAG_MEMBER,
113 GOTAG_ANONMEMBER,
114 GOTAG_METHODSPEC,
115 GOTAG_UNKNOWN,
116 GOTAG_PACKAGE_NAME,
117 GOTAG_TALIAS,
118 GOTAG_RECEIVER,
119 } goKind;
120
121 typedef enum {
122 R_GOTAG_PACKAGE_IMPORTED,
123 } GoPackageRole;
124
125 static roleDefinition GoPackageRoles [] = {
126 { true, "imported", "imported package" },
127 };
128
129 typedef enum {
130 R_GOTAG_UNKNOWN_RECEIVER,
131 } GoUnknownRole;
132
133 static roleDefinition GoUnknownRoles [] = {
134 { true, "receiverType", "receiver type" },
135 };
136
137 static kindDefinition GoKinds[] = {
138 {true, 'p', "package", "packages",
139 .referenceOnly = false, ATTACH_ROLES (GoPackageRoles)},
140 {true, 'f', "func", "functions"},
141 {true, 'c', "const", "constants"},
142 {true, 't', "type", "types"},
143 {true, 'v', "var", "variables"},
144 {true, 's', "struct", "structs"},
145 {true, 'i', "interface", "interfaces"},
146 {true, 'm', "member", "struct members"},
147 {true, 'M', "anonMember", "struct anonymous members"},
148 {true, 'n', "methodSpec", "interface method specification"},
149 {true, 'u', "unknown", "unknown",
150 .referenceOnly = true, ATTACH_ROLES (GoUnknownRoles)},
151 {true, 'P', "packageName", "name for specifying imported package"},
152 {true, 'a', "talias", "type aliases"},
153 {false,'R', "receiver", "receivers"},
154 };
155
156 static const keywordTable GoKeywordTable[] = {
157 {"package", KEYWORD_package},
158 {"import", KEYWORD_import},
159 {"const", KEYWORD_const},
160 {"type", KEYWORD_type},
161 {"var", KEYWORD_var},
162 {"func", KEYWORD_func},
163 {"struct", KEYWORD_struct},
164 {"interface", KEYWORD_interface},
165 {"map", KEYWORD_map},
166 {"chan", KEYWORD_chan}
167 };
168
169 typedef enum {
170 F_PACKAGE,
171 F_PACKAGE_NAME,
172 F_HOW_IMPORTED,
173 } goField;
174
175 static fieldDefinition GoFields [] = {
176 {
177 .name = "package",
178 .description = "the real package specified by the package name",
179 .enabled = true,
180 },
181 {
182 .name = "packageName",
183 .description = "the name for referring the package",
184 .enabled = true,
185 },
186 {
187 .name = "howImported",
188 .description = "how the package is imported (\"inline\" for `.' or \"init\" for `_')",
189 .enabled = false,
190 },
191 };
192
193
194 /*
195 * FUNCTION DEFINITIONS
196 */
197
newPoolToken(void * createArg CTAGS_ATTR_UNUSED)198 static void *newPoolToken (void *createArg CTAGS_ATTR_UNUSED)
199 {
200 tokenInfo *const token = xMalloc (1, tokenInfo);
201 token->string = vStringNew ();
202 return token;
203 }
204
clearPoolToken(void * data)205 static void clearPoolToken (void *data)
206 {
207 tokenInfo *token = data;
208
209 token->type = TOKEN_NONE;
210 token->keyword = KEYWORD_NONE;
211 token->lineNumber = getInputLineNumber ();
212 token->filePosition = getInputFilePosition ();
213 vStringClear (token->string);
214 }
215
copyToken(tokenInfo * const dest,const tokenInfo * const other)216 static void copyToken (tokenInfo *const dest, const tokenInfo *const other)
217 {
218 dest->type = other->type;
219 dest->keyword = other->keyword;
220 vStringCopy(dest->string, other->string);
221 dest->lineNumber = other->lineNumber;
222 dest->filePosition = other->filePosition;
223 }
224
deletePoolToken(void * data)225 static void deletePoolToken (void* data)
226 {
227 tokenInfo * const token = data;
228
229 vStringDelete (token->string);
230 eFree (token);
231 }
232
initialize(const langType language)233 static void initialize (const langType language)
234 {
235 Lang_go = language;
236 TokenPool = objPoolNew (16, newPoolToken, deletePoolToken, clearPoolToken, NULL);
237 }
238
finalize(const langType language,bool initialized)239 static void finalize (const langType language, bool initialized)
240 {
241 if (!initialized)
242 return;
243
244 objPoolDelete (TokenPool);
245 }
246
247 /*
248 * Parsing functions
249 */
250
parseString(vString * const string,const int delimiter)251 static void parseString (vString *const string, const int delimiter)
252 {
253 bool end = false;
254 while (!end)
255 {
256 int c = getcFromInputFile ();
257 if (c == EOF)
258 end = true;
259 else if (c == '\\' && delimiter != '`')
260 {
261 c = getcFromInputFile ();
262 if (c != '\'' && c != '\"')
263 vStringPut (string, '\\');
264 vStringPut (string, c);
265 }
266 else if (c == delimiter)
267 end = true;
268 else
269 vStringPut (string, c);
270 }
271 }
272
parseIdentifier(vString * const string,const int firstChar)273 static void parseIdentifier (vString *const string, const int firstChar)
274 {
275 int c = firstChar;
276 do
277 {
278 vStringPut (string, c);
279 c = getcFromInputFile ();
280 } while (isIdentChar (c));
281 ungetcToInputFile (c); /* always unget, LF might add a semicolon */
282 }
283
collectorIsEmpty(collector * collector)284 static bool collectorIsEmpty(collector *collector)
285 {
286 return !vStringLength(collector->str);
287 }
288
collectorPut(collector * collector,char c)289 static void collectorPut (collector *collector, char c)
290 {
291 if ((vStringLength(collector->str) > 2)
292 && strcmp (vStringValue (collector->str) + (vStringLength(collector->str) - 3),
293 "...") == 0
294 && c == ' ')
295 return;
296 else if (vStringLength(collector->str) > 0)
297 {
298 if (vStringLast(collector->str) == '(' && c == ' ')
299 return;
300 else if (vStringLast(collector->str) == ' ' && c == ')')
301 vStringChop(collector->str);
302 }
303
304 collector->last_len = vStringLength (collector->str);
305 vStringPut (collector->str, c);
306 }
307
collectorCatS(collector * collector,char * cstr)308 static void collectorCatS (collector *collector, char *cstr)
309 {
310 collector->last_len = vStringLength (collector->str);
311 vStringCatS (collector->str, cstr);
312 }
313
collectorCat(collector * collector,vString * str)314 static void collectorCat (collector *collector, vString *str)
315 {
316 collector->last_len = vStringLength (collector->str);
317 vStringCat (collector->str, str);
318 }
319
collectorAppendToken(collector * collector,const tokenInfo * const token)320 static void collectorAppendToken (collector *collector, const tokenInfo *const token)
321 {
322 if (token->type == TOKEN_LEFT_ARROW)
323 collectorCatS (collector, "<-");
324 else if (token->type == TOKEN_STRING)
325 {
326 // only struct member annotations can appear in function prototypes
327 // so only `` type strings are possible
328 collector->last_len = vStringLength (collector->str);
329 vStringPut(collector->str, '`');
330 vStringCat(collector->str, token->string);
331 vStringPut(collector->str, '`');
332 }
333 else if (token->type == TOKEN_IDENTIFIER || token->type == TOKEN_KEYWORD)
334 collectorCat (collector, token->string);
335 else if (token->type == TOKEN_3DOTS)
336 {
337 if ((vStringLength (collector->str) > 0)
338 && vStringLast(collector->str) != ' ')
339 collectorPut (collector, ' ');
340 collectorCatS (collector, "...");
341 }
342 else if (token->c != EOF)
343 collectorPut (collector, token->c);
344 }
345
collectorTruncate(collector * collector,bool dropLast)346 static void collectorTruncate (collector *collector, bool dropLast)
347 {
348 if (dropLast)
349 vStringTruncate (collector->str, collector->last_len);
350
351 vStringStripLeading (collector->str);
352 vStringStripTrailing (collector->str);
353 }
354
readTokenFull(tokenInfo * const token,collector * collector)355 static void readTokenFull (tokenInfo *const token, collector *collector)
356 {
357 int c;
358 static tokenType lastTokenType = TOKEN_NONE;
359 bool firstWhitespace = true;
360 bool whitespace;
361
362 token->c = EOF;
363 token->type = TOKEN_NONE;
364 token->keyword = KEYWORD_NONE;
365 vStringClear (token->string);
366
367 getNextChar:
368 do
369 {
370 c = getcFromInputFile ();
371 token->lineNumber = getInputLineNumber ();
372 token->filePosition = getInputFilePosition ();
373 if (c == '\n' && (lastTokenType == TOKEN_IDENTIFIER ||
374 lastTokenType == TOKEN_STRING ||
375 lastTokenType == TOKEN_OTHER ||
376 lastTokenType == TOKEN_CLOSE_PAREN ||
377 lastTokenType == TOKEN_CLOSE_CURLY ||
378 lastTokenType == TOKEN_CLOSE_SQUARE))
379 {
380 c = ';'; // semicolon injection
381 }
382 whitespace = c == '\t' || c == ' ' || c == '\r' || c == '\n';
383 if (collector && whitespace && firstWhitespace && vStringLength (collector->str) < MAX_COLLECTOR_LENGTH)
384 {
385 firstWhitespace = false;
386 collectorPut (collector, ' ');
387 }
388 }
389 while (whitespace);
390
391 switch (c)
392 {
393 case EOF:
394 token->type = TOKEN_EOF;
395 break;
396
397 case ';':
398 token->type = TOKEN_SEMICOLON;
399 break;
400
401 case '/':
402 {
403 bool hasNewline = false;
404 int d = getcFromInputFile ();
405 switch (d)
406 {
407 case '/':
408 skipToCharacterInInputFile ('\n');
409 /* Line comments start with the
410 * character sequence // and
411 * continue through the next
412 * newline. A line comment acts
413 * like a newline. */
414 ungetcToInputFile ('\n');
415 goto getNextChar;
416 case '*':
417 do
418 {
419 do
420 {
421 d = getcFromInputFile ();
422 if (d == '\n')
423 {
424 hasNewline = true;
425 }
426 } while (d != EOF && d != '*');
427
428 c = getcFromInputFile ();
429 if (c == '/')
430 break;
431 else
432 ungetcToInputFile (c);
433 } while (c != EOF && c != '\0');
434
435 ungetcToInputFile (hasNewline ? '\n' : ' ');
436 goto getNextChar;
437 default:
438 token->type = TOKEN_OTHER;
439 ungetcToInputFile (d);
440 break;
441 }
442 }
443 break;
444
445 case '"':
446 case '\'':
447 case '`':
448 token->type = TOKEN_STRING;
449 parseString (token->string, c);
450 token->lineNumber = getInputLineNumber ();
451 token->filePosition = getInputFilePosition ();
452 break;
453
454 case '<':
455 {
456 int d = getcFromInputFile ();
457 if (d == '-')
458 token->type = TOKEN_LEFT_ARROW;
459 else
460 {
461 ungetcToInputFile (d);
462 token->type = TOKEN_OTHER;
463 }
464 }
465 break;
466
467 case '(':
468 token->type = TOKEN_OPEN_PAREN;
469 break;
470
471 case ')':
472 token->type = TOKEN_CLOSE_PAREN;
473 break;
474
475 case '{':
476 token->type = TOKEN_OPEN_CURLY;
477 break;
478
479 case '}':
480 token->type = TOKEN_CLOSE_CURLY;
481 break;
482
483 case '[':
484 token->type = TOKEN_OPEN_SQUARE;
485 break;
486
487 case ']':
488 token->type = TOKEN_CLOSE_SQUARE;
489 break;
490
491 case '*':
492 token->type = TOKEN_STAR;
493 break;
494
495 case '.':
496 {
497 int d, e;
498 d = getcFromInputFile ();
499 if (d == '.')
500 {
501 e = getcFromInputFile ();
502 if (e == '.')
503 {
504 token->type = TOKEN_3DOTS;
505 break;
506 }
507 else
508 {
509 ungetcToInputFile (e);
510 ungetcToInputFile (d);
511 }
512 }
513 else
514 ungetcToInputFile (d);
515 }
516 token->type = TOKEN_DOT;
517 break;
518
519 case ',':
520 token->type = TOKEN_COMMA;
521 break;
522
523 case '=':
524 token->type = TOKEN_EQUAL;
525 break;
526
527 default:
528 if (isStartIdentChar (c))
529 {
530 parseIdentifier (token->string, c);
531 token->lineNumber = getInputLineNumber ();
532 token->filePosition = getInputFilePosition ();
533 token->keyword = lookupKeyword (vStringValue (token->string), Lang_go);
534 if (isKeyword (token, KEYWORD_NONE))
535 token->type = TOKEN_IDENTIFIER;
536 else
537 token->type = TOKEN_KEYWORD;
538 }
539 else
540 token->type = TOKEN_OTHER;
541 break;
542 }
543
544 token->c = c;
545
546 if (collector && vStringLength (collector->str) < MAX_COLLECTOR_LENGTH)
547 collectorAppendToken (collector, token);
548
549 lastTokenType = token->type;
550 }
551
readToken(tokenInfo * const token)552 static void readToken (tokenInfo *const token)
553 {
554 readTokenFull (token, NULL);
555 }
556
skipToMatchedNoRead(tokenInfo * const token,collector * collector)557 static bool skipToMatchedNoRead (tokenInfo *const token, collector *collector)
558 {
559 int nest_level = 0;
560 tokenType open_token = token->type;
561 tokenType close_token;
562
563 switch (open_token)
564 {
565 case TOKEN_OPEN_PAREN:
566 close_token = TOKEN_CLOSE_PAREN;
567 break;
568 case TOKEN_OPEN_CURLY:
569 close_token = TOKEN_CLOSE_CURLY;
570 break;
571 case TOKEN_OPEN_SQUARE:
572 close_token = TOKEN_CLOSE_SQUARE;
573 break;
574 default:
575 return false;
576 }
577
578 /*
579 * This routine will skip to a matching closing token.
580 * It will also handle nested tokens.
581 */
582 nest_level++;
583 while (nest_level > 0 && !isType (token, TOKEN_EOF))
584 {
585 readTokenFull (token, collector);
586 if (isType (token, open_token))
587 nest_level++;
588 else if (isType (token, close_token))
589 nest_level--;
590 }
591
592 return true;
593 }
594
skipToMatched(tokenInfo * const token,collector * collector)595 static void skipToMatched (tokenInfo *const token, collector *collector)
596 {
597 if (skipToMatchedNoRead (token, collector))
598 readTokenFull (token, collector);
599 }
600
skipType(tokenInfo * const token,collector * collector)601 static bool skipType (tokenInfo *const token, collector *collector)
602 {
603 // Type = TypeName | TypeLit | "(" Type ")" .
604 // Skips also function multiple return values "(" Type {"," Type} ")"
605 if (isType (token, TOKEN_OPEN_PAREN))
606 {
607 skipToMatched (token, collector);
608 return true;
609 }
610
611 // TypeName = QualifiedIdent.
612 // QualifiedIdent = [ PackageName "." ] identifier .
613 // PackageName = identifier .
614 if (isType (token, TOKEN_IDENTIFIER))
615 {
616 readTokenFull (token, collector);
617 if (isType (token, TOKEN_DOT))
618 {
619 readTokenFull (token, collector);
620 if (isType (token, TOKEN_IDENTIFIER))
621 readTokenFull (token, collector);
622 }
623 return true;
624 }
625
626 // StructType = "struct" "{" { FieldDecl ";" } "}"
627 // InterfaceType = "interface" "{" { MethodSpec ";" } "}" .
628 if (isKeyword (token, KEYWORD_struct) || isKeyword (token, KEYWORD_interface))
629 {
630 readTokenFull (token, collector);
631 // skip over "{}"
632 skipToMatched (token, collector);
633 return true;
634 }
635
636 // ArrayType = "[" ArrayLength "]" ElementType .
637 // SliceType = "[" "]" ElementType .
638 // ElementType = Type .
639 if (isType (token, TOKEN_OPEN_SQUARE))
640 {
641 skipToMatched (token, collector);
642 return skipType (token, collector);
643 }
644
645 // PointerType = "*" BaseType .
646 // BaseType = Type .
647 // ChannelType = ( "chan" [ "<-" ] | "<-" "chan" ) ElementType .
648 if (isType (token, TOKEN_STAR) || isKeyword (token, KEYWORD_chan) || isType (token, TOKEN_LEFT_ARROW))
649 {
650 readTokenFull (token, collector);
651 return skipType (token, collector);
652 }
653
654 // MapType = "map" "[" KeyType "]" ElementType .
655 // KeyType = Type .
656 if (isKeyword (token, KEYWORD_map))
657 {
658 readTokenFull (token, collector);
659 // skip over "[]"
660 skipToMatched (token, collector);
661 return skipType (token, collector);
662 }
663
664 // FunctionType = "func" Signature .
665 // Signature = Parameters [ Result ] .
666 // Result = Parameters | Type .
667 // Parameters = "(" [ ParameterList [ "," ] ] ")" .
668 if (isKeyword (token, KEYWORD_func))
669 {
670 readTokenFull (token, collector);
671 // Parameters, skip over "()"
672 skipToMatched (token, collector);
673 // Result is parameters or type or nothing. skipType treats anything
674 // surrounded by parentheses as a type, and does nothing if what
675 // follows is not a type.
676 return skipType (token, collector);
677 }
678
679 return false;
680 }
681
makeTagFull(tokenInfo * const token,const goKind kind,const int scope,const char * argList,const char * typeref,const int role)682 static int makeTagFull (tokenInfo *const token, const goKind kind,
683 const int scope, const char *argList, const char *typeref,
684 const int role)
685 {
686 const char *const name = vStringValue (token->string);
687
688 tagEntryInfo e;
689
690 /* Don't record `_' placeholder variable */
691 if (kind == GOTAG_VAR && name[0] == '_' && name[1] == '\0')
692 return CORK_NIL;
693
694 initRefTagEntry (&e, name, kind, role);
695
696 e.lineNumber = token->lineNumber;
697 e.filePosition = token->filePosition;
698 if (argList)
699 e.extensionFields.signature = argList;
700 if (typeref)
701 {
702 /* Follows Cxx parser convention */
703 e.extensionFields.typeRef [0] = "typename";
704 e.extensionFields.typeRef [1] = typeref;
705 }
706
707 e.extensionFields.scopeIndex = scope;
708 return makeTagEntry (&e);
709 }
710
makeTag(tokenInfo * const token,const goKind kind,const int scope,const char * argList,const char * typeref)711 static int makeTag (tokenInfo *const token, const goKind kind,
712 const int scope, const char *argList, const char *typeref)
713 {
714 return makeTagFull (token, kind, scope, argList, typeref,
715 ROLE_DEFINITION_INDEX);
716 }
717
makeRefTag(tokenInfo * const token,const goKind kind,const int role)718 static int makeRefTag (tokenInfo *const token, const goKind kind,
719 const int role)
720 {
721 return makeTagFull (token, kind, CORK_NIL, NULL, NULL, role);
722 }
723
parsePackage(tokenInfo * const token)724 static int parsePackage (tokenInfo *const token)
725 {
726 readToken (token);
727 if (isType (token, TOKEN_IDENTIFIER))
728 {
729 return makeTag (token, GOTAG_PACKAGE, CORK_NIL, NULL, NULL);
730 }
731 else
732 return CORK_NIL;
733 }
734
parseReceiver(tokenInfo * const token,int * corkIndex)735 static tokenInfo * parseReceiver (tokenInfo *const token, int *corkIndex)
736 {
737 tokenInfo *receiver_type_token = NULL;
738 int nest_level = 1;
739
740 *corkIndex = CORK_NIL;
741
742 /* Looking for an identifier before ')'. */
743 while (nest_level > 0 && !isType (token, TOKEN_EOF))
744 {
745 if (isType (token, TOKEN_IDENTIFIER))
746 {
747 if (*corkIndex == CORK_NIL)
748 *corkIndex = makeTag (token, GOTAG_RECEIVER, CORK_NIL, NULL, NULL);
749 if (!receiver_type_token)
750 receiver_type_token = newToken ();
751 copyToken (receiver_type_token, token);
752 }
753
754 readToken (token);
755 if (isType (token, TOKEN_OPEN_PAREN))
756 nest_level++;
757 else if (isType (token, TOKEN_CLOSE_PAREN))
758 nest_level--;
759 }
760
761 if (nest_level > 0 && receiver_type_token)
762 {
763 deleteToken (receiver_type_token);
764 receiver_type_token = NULL;
765 }
766
767 if (receiver_type_token)
768 {
769 tagEntryInfo *e = getEntryInCorkQueue (*corkIndex);
770 if (e)
771 {
772 e->extensionFields.typeRef [0] = eStrdup ("typename");
773 e->extensionFields.typeRef [1] = vStringStrdup (receiver_type_token->string);
774 }
775 }
776 readToken (token);
777 return receiver_type_token;
778 }
779
parseFunctionOrMethod(tokenInfo * const token,const int scope)780 static void parseFunctionOrMethod (tokenInfo *const token, const int scope)
781 {
782 int receiver_cork = CORK_NIL;
783 tokenInfo *receiver_type_token = NULL;
784
785 // FunctionDecl = "func" identifier Signature [ Body ] .
786 // Body = Block.
787 //
788 // MethodDecl = "func" Receiver MethodName Signature [ Body ] .
789 // Receiver = "(" [ identifier ] [ "*" ] BaseTypeName ")" .
790 // BaseTypeName = identifier .
791
792 // Pick up receiver type.
793 readToken (token);
794 if (isType (token, TOKEN_OPEN_PAREN))
795 receiver_type_token = parseReceiver (token, &receiver_cork);
796
797 if (isType (token, TOKEN_IDENTIFIER))
798 {
799 int cork;
800 tagEntryInfo *e = NULL;
801 tokenInfo *functionToken = newToken ();
802 int func_scope;
803
804 copyToken (functionToken, token);
805
806 // Start recording signature
807 vString *buffer = vStringNew ();
808 collector collector = { .str = buffer, .last_len = 0, };
809
810 // Skip over parameters.
811 readTokenFull (token, &collector);
812 skipToMatchedNoRead (token, &collector);
813
814 collectorTruncate (&collector, false);
815 if (receiver_type_token)
816 {
817 func_scope = anyEntryInScope (scope, vStringValue (receiver_type_token->string), false);
818 if (func_scope == CORK_NIL)
819 func_scope = makeTagFull(receiver_type_token, GOTAG_UNKNOWN,
820 scope, NULL, NULL,
821 R_GOTAG_UNKNOWN_RECEIVER);
822 }
823 else
824 func_scope = scope;
825
826 cork = makeTag (functionToken, GOTAG_FUNCTION,
827 func_scope, vStringValue (buffer), NULL);
828 if ((e = getEntryInCorkQueue (cork)))
829 {
830 tagEntryInfo *receiver = getEntryInCorkQueue (receiver_cork);
831 if (receiver)
832 receiver->extensionFields.scopeIndex = cork;
833 }
834
835 deleteToken (functionToken);
836
837 vStringClear (collector.str);
838 collector.last_len = 0;
839
840 readTokenFull (token, &collector);
841
842 // Skip over result.
843 skipType (token, &collector);
844
845 // Neither "{" nor " {".
846 if (!(isType (token, TOKEN_OPEN_CURLY) && collector.last_len < 2))
847 {
848 collectorTruncate(&collector, isType (token, TOKEN_OPEN_CURLY));
849 if (e)
850 {
851 e->extensionFields.typeRef [0] = eStrdup ("typename");
852 e->extensionFields.typeRef [1] = vStringDeleteUnwrap (buffer);
853 buffer = NULL;
854 }
855 }
856
857 if (buffer)
858 vStringDelete (buffer);
859
860 // Skip over function body.
861 if (isType (token, TOKEN_OPEN_CURLY))
862 {
863 skipToMatched (token, NULL);
864 if (e)
865 e->extensionFields.endLine = getInputLineNumber ();
866 }
867 }
868
869 if (receiver_type_token)
870 deleteToken(receiver_type_token);
871 }
872
attachTypeRefField(int scope,intArray * corks,const char * const type)873 static void attachTypeRefField (int scope, intArray *corks, const char *const type)
874 {
875 int type_cork = anyEntryInScope (scope, type, false);
876 tagEntryInfo *type_e = getEntryInCorkQueue (type_cork);
877
878 for (unsigned int i = 0; i < intArrayCount (corks); i++)
879 {
880 int cork = intArrayItem (corks, i);
881 tagEntryInfo *e = getEntryInCorkQueue (cork);
882 if (!e)
883 continue;
884 e->extensionFields.typeRef [0] = eStrdup (type_e
885 ?GoKinds[type_e->kindIndex].name
886 :"typename");
887 e->extensionFields.typeRef [1] = eStrdup (type);
888 }
889 }
890
parseInterfaceMethods(tokenInfo * const token,const int scope)891 static void parseInterfaceMethods (tokenInfo *const token, const int scope)
892 {
893 // InterfaceType = "interface" "{" { MethodSpec ";" } "}" .
894 // MethodSpec = MethodName Signature | InterfaceTypeName .
895 // MethodName = identifier .
896 // InterfaceTypeName = TypeName .
897
898 vString *inheritsBuf = vStringNew ();
899 collector inherits = { .str = inheritsBuf, .last_len = 0, };
900
901 readToken (token);
902 if (!isType (token, TOKEN_OPEN_CURLY))
903 return;
904
905 readToken (token);
906 while (!isType (token, TOKEN_EOF) && !isType (token, TOKEN_CLOSE_CURLY))
907 {
908 if (isType (token, TOKEN_IDENTIFIER))
909 {
910 tokenInfo * headToken = newToken();
911 copyToken (headToken, token);
912
913 readToken (token);
914 if(isType (token, TOKEN_DOT))
915 {
916 if (!collectorIsEmpty(&inherits))
917 collectorPut (&inherits, ',');
918 collectorAppendToken (&inherits, headToken);
919 readTokenFull (token, NULL);
920 if (isType (token, TOKEN_IDENTIFIER))
921 {
922 collectorPut (&inherits, '.');
923 collectorAppendToken (&inherits, token);
924 readToken (token);
925 }
926 /* If the token is not an identifier, the input
927 may be wrong. */
928 }
929 else if (isType (token, TOKEN_SEMICOLON))
930 {
931 if (!collectorIsEmpty(&inherits))
932 collectorPut (&inherits, ',');
933 collectorAppendToken (&inherits, headToken);
934 readToken (token);
935 }
936 else if (isType (token, TOKEN_OPEN_PAREN))
937 {
938 // => Signature
939 // Signature = Parameters [ Result ] .
940 vString *pbuf = vStringNew ();
941 collector pcol = { .str = pbuf, .last_len = 0, };
942 vString *rbuf = NULL;
943 collector rcol = { .str = NULL, .last_len = 0, };
944
945 // Parameters
946 collectorPut (&pcol, '(');
947 skipToMatched (token, &pcol);
948 collectorTruncate(&pcol, true);
949
950 if (!isType (token, TOKEN_SEMICOLON))
951 {
952 rbuf = vStringNew ();
953 rcol.str = rbuf;
954
955 collectorAppendToken (&rcol, token);
956 skipType (token, &rcol);
957 collectorTruncate(&rcol, true);
958 }
959
960 makeTag (headToken, GOTAG_METHODSPEC, scope,
961 vStringValue (pbuf),
962 rbuf? vStringValue(rbuf): NULL);
963
964 if (rbuf)
965 vStringDelete (rbuf);
966 vStringDelete (pbuf);
967 }
968 deleteToken (headToken);
969 }
970 else
971 readToken (token);
972 }
973
974 if (!collectorIsEmpty(&inherits))
975 {
976 tagEntryInfo *e = getEntryInCorkQueue (scope);
977 if (e)
978 {
979 e->extensionFields.inheritance = vStringDeleteUnwrap (inheritsBuf);
980 inheritsBuf = NULL;
981 }
982 }
983 vStringDelete (inheritsBuf);
984 }
985
parseStructMembers(tokenInfo * const token,const int scope)986 static void parseStructMembers (tokenInfo *const token, const int scope)
987 {
988 // StructType = "struct" "{" { FieldDecl ";" } "}" .
989 // FieldDecl = (IdentifierList Type | AnonymousField) [ Tag ] .
990 // AnonymousField = [ "*" ] TypeName .
991 // Tag = string_lit .
992
993 readToken (token);
994 if (!isType (token, TOKEN_OPEN_CURLY))
995 return;
996
997 vString *typeForAnonMember = vStringNew ();
998 intArray *corkForFields = intArrayNew ();
999
1000 readToken (token);
1001 while (!isType (token, TOKEN_EOF) && !isType (token, TOKEN_CLOSE_CURLY))
1002 {
1003 tokenInfo *memberCandidate = NULL;
1004 bool first = true;
1005
1006 while (!isType (token, TOKEN_EOF))
1007 {
1008 if (isType (token, TOKEN_IDENTIFIER))
1009 {
1010 if (first)
1011 {
1012 // could be anonymous field like in 'struct {int}' - we don't know yet
1013 memberCandidate = newToken ();
1014 copyToken (memberCandidate, token);
1015 first = false;
1016 }
1017 else
1018 {
1019 int cork;
1020 if (memberCandidate)
1021 {
1022 // if we are here, there was a comma and memberCandidate isn't an anonymous field
1023 cork = makeTag (memberCandidate, GOTAG_MEMBER, scope, NULL, NULL);
1024 deleteToken (memberCandidate);
1025 memberCandidate = NULL;
1026 intArrayAdd (corkForFields, cork);
1027 }
1028 cork = makeTag (token, GOTAG_MEMBER, scope, NULL, NULL);
1029 intArrayAdd (corkForFields, cork);
1030 }
1031 readToken (token);
1032 }
1033 if (!isType (token, TOKEN_COMMA))
1034 break;
1035 readToken (token);
1036 }
1037
1038 if (first && isType (token, TOKEN_STAR))
1039 {
1040 vStringPut (typeForAnonMember, '*');
1041 readToken (token);
1042 }
1043 else if (memberCandidate &&
1044 (isType (token, TOKEN_DOT) ||
1045 isType (token, TOKEN_STRING) ||
1046 isType (token, TOKEN_SEMICOLON)))
1047 // memberCandidate is part of anonymous type
1048 vStringCat (typeForAnonMember, memberCandidate->string);
1049
1050 // the above two cases that set typeForAnonMember guarantee
1051 // this is an anonymous member
1052 if (vStringLength (typeForAnonMember) > 0)
1053 {
1054 tokenInfo *anonMember = NULL;
1055
1056 if (memberCandidate)
1057 {
1058 anonMember = newToken ();
1059 copyToken (anonMember, memberCandidate);
1060 }
1061
1062 // TypeName of AnonymousField has a dot like package"."type.
1063 // Pick up the last package component, and store it to
1064 // memberCandidate.
1065 while (isType (token, TOKEN_IDENTIFIER) ||
1066 isType (token, TOKEN_DOT))
1067 {
1068 if (isType (token, TOKEN_IDENTIFIER))
1069 {
1070 if (!anonMember)
1071 anonMember = newToken ();
1072 copyToken (anonMember, token);
1073 vStringCat (typeForAnonMember, anonMember->string);
1074 }
1075 else if (isType (token, TOKEN_DOT))
1076 vStringPut (typeForAnonMember, '.');
1077 readToken (token);
1078 }
1079
1080 // optional tag
1081 if (isType (token, TOKEN_STRING))
1082 readToken (token);
1083
1084 if (anonMember)
1085 {
1086 makeTag (anonMember, GOTAG_ANONMEMBER, scope, NULL,
1087 vStringValue (typeForAnonMember));
1088 deleteToken (anonMember);
1089 }
1090 }
1091 else
1092 {
1093 vString *typeForMember = vStringNew ();
1094 collector collector = { .str = typeForMember, .last_len = 0, };
1095
1096 collectorAppendToken (&collector, token);
1097 skipType (token, &collector);
1098 collectorTruncate (&collector, true);
1099
1100 if (memberCandidate)
1101 makeTag (memberCandidate, GOTAG_MEMBER, scope, NULL,
1102 vStringValue (typeForMember));
1103
1104 attachTypeRefField (scope, corkForFields, vStringValue (typeForMember));
1105 intArrayClear (corkForFields);
1106 vStringDelete (typeForMember);
1107 }
1108
1109 if (memberCandidate)
1110 deleteToken (memberCandidate);
1111
1112 vStringClear (typeForAnonMember);
1113
1114 while (!isType (token, TOKEN_SEMICOLON) && !isType (token, TOKEN_CLOSE_CURLY)
1115 && !isType (token, TOKEN_EOF))
1116 {
1117 readToken (token);
1118 skipToMatched (token, NULL);
1119 }
1120
1121 if (!isType (token, TOKEN_CLOSE_CURLY))
1122 {
1123 // we are at TOKEN_SEMICOLON
1124 readToken (token);
1125 }
1126 }
1127
1128 intArrayDelete (corkForFields);
1129 vStringDelete (typeForAnonMember);
1130 }
1131
parseConstTypeVar(tokenInfo * const token,goKind kind,const int scope)1132 static void parseConstTypeVar (tokenInfo *const token, goKind kind, const int scope)
1133 {
1134 // ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
1135 // ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
1136 // IdentifierList = identifier { "," identifier } .
1137 // ExpressionList = Expression { "," Expression } .
1138 // TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
1139 // TypeSpec = identifier [ "=" ] Type .
1140 // VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
1141 // VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
1142 bool usesParens = false;
1143 intArray *corks
1144 = (kind == GOTAG_VAR || kind == GOTAG_CONST)? intArrayNew (): NULL;
1145
1146 readToken (token);
1147
1148 if (isType (token, TOKEN_OPEN_PAREN))
1149 {
1150 usesParens = true;
1151 readToken (token);
1152 }
1153
1154 do
1155 {
1156 tokenInfo *typeToken = NULL;
1157 int member_scope = scope;
1158
1159 while (!isType (token, TOKEN_EOF))
1160 {
1161 if (isType (token, TOKEN_IDENTIFIER))
1162 {
1163 if (kind == GOTAG_TYPE)
1164 {
1165 typeToken = newToken ();
1166 copyToken (typeToken, token);
1167 readToken (token);
1168 if (isType (token, TOKEN_EQUAL))
1169 {
1170 kind = GOTAG_TALIAS;
1171 readToken (token);
1172 }
1173
1174 if (isKeyword (token, KEYWORD_struct))
1175 member_scope = makeTag (typeToken, GOTAG_STRUCT,
1176 scope, NULL, NULL);
1177 else if (isKeyword (token, KEYWORD_interface))
1178 member_scope = makeTag (typeToken, GOTAG_INTERFACE,
1179 scope, NULL, NULL);
1180 else
1181 member_scope = makeTag (typeToken, kind,
1182 scope, NULL, NULL);
1183
1184 if (member_scope != CORK_NIL)
1185 registerEntry (member_scope);
1186 break;
1187 }
1188 else
1189 {
1190 int c = makeTag (token, kind, scope, NULL, NULL);
1191 if (c != CORK_NIL && corks)
1192 intArrayAdd (corks, c);
1193 }
1194 readToken (token);
1195 }
1196 if (!isType (token, TOKEN_COMMA))
1197 break;
1198 readToken (token);
1199 }
1200
1201 if (typeToken)
1202 {
1203 if (isKeyword (token, KEYWORD_struct))
1204 parseStructMembers (token, member_scope);
1205 else if (isKeyword (token, KEYWORD_interface))
1206 parseInterfaceMethods (token, member_scope);
1207 else
1208 {
1209 /* Filling "typeref:" field of typeToken. */
1210 vString *buffer = vStringNew ();
1211 collector collector = { .str = buffer, .last_len = 0, };
1212
1213 collectorAppendToken (&collector, token);
1214 skipType (token, &collector);
1215 collectorTruncate (&collector, true);
1216
1217 if ((member_scope != CORK_NIL) && !vStringIsEmpty (buffer))
1218 {
1219 tagEntryInfo *e = getEntryInCorkQueue (member_scope);
1220 if (e)
1221 {
1222 e->extensionFields.typeRef [0] = eStrdup ("typename");
1223 e->extensionFields.typeRef [1] = vStringDeleteUnwrap (buffer);
1224 }
1225 }
1226 else
1227 vStringDelete (buffer);
1228 }
1229 deleteToken (typeToken);
1230 }
1231 else if (corks)
1232 {
1233 vString *buffer = vStringNew ();
1234 collector collector = { .str = buffer, .last_len = 0, };
1235
1236 collectorAppendToken (&collector, token);
1237 skipType (token, &collector);
1238 collectorTruncate (&collector, true);
1239
1240 if (!vStringIsEmpty (buffer))
1241 attachTypeRefField (scope, corks, vStringValue (buffer));
1242 vStringDelete (buffer);
1243 intArrayClear (corks);
1244 }
1245 else
1246 skipType (token, NULL);
1247
1248 while (!isType (token, TOKEN_SEMICOLON) && !isType (token, TOKEN_CLOSE_PAREN)
1249 && !isType (token, TOKEN_EOF))
1250 {
1251 readToken (token);
1252 skipToMatched (token, NULL);
1253 }
1254
1255 if (member_scope != scope && member_scope != CORK_NIL)
1256 {
1257 tagEntryInfo *e = getEntryInCorkQueue (member_scope);
1258 if (e)
1259 e->extensionFields.endLine = getInputLineNumber ();
1260 }
1261
1262 if (usesParens && !isType (token, TOKEN_CLOSE_PAREN))
1263 {
1264 // we are at TOKEN_SEMICOLON
1265 readToken (token);
1266 }
1267 }
1268 while (!isType (token, TOKEN_EOF) &&
1269 usesParens && !isType (token, TOKEN_CLOSE_PAREN));
1270
1271 intArrayDelete (corks);
1272 }
1273
parseImportSpec(tokenInfo * const token)1274 static void parseImportSpec (tokenInfo *const token)
1275 {
1276 // ImportSpec = [ "." | PackageName ] ImportPath .
1277 // ImportPath = string_lit .
1278
1279 int packageName_cork = CORK_NIL;
1280 char *how_imported = NULL;
1281 if (isType (token, TOKEN_IDENTIFIER))
1282 {
1283 if (strcmp(vStringValue (token->string), "_") == 0)
1284 how_imported = "init";
1285 else
1286 {
1287 packageName_cork = makeTag (token, GOTAG_PACKAGE_NAME,
1288 CORK_NIL, NULL, NULL);
1289 }
1290 readToken (token);
1291 }
1292 else if (isType (token, TOKEN_DOT))
1293 {
1294 how_imported = "inline";
1295 readToken (token);
1296 }
1297
1298 if (isType (token, TOKEN_STRING))
1299 {
1300 int package_cork =
1301 makeRefTag (token, GOTAG_PACKAGE, R_GOTAG_PACKAGE_IMPORTED);
1302
1303 if (package_cork != CORK_NIL && how_imported)
1304 attachParserFieldToCorkEntry (package_cork,
1305 GoFields [F_HOW_IMPORTED].ftype,
1306 how_imported);
1307
1308 if (packageName_cork != CORK_NIL)
1309 {
1310 attachParserFieldToCorkEntry (packageName_cork,
1311 GoFields [F_PACKAGE].ftype,
1312 vStringValue (token->string));
1313 if (package_cork != CORK_NIL)
1314 {
1315 tagEntryInfo *e = getEntryInCorkQueue (packageName_cork);
1316 if (e)
1317 attachParserFieldToCorkEntry (package_cork,
1318 GoFields [F_PACKAGE_NAME].ftype,
1319 e->name);
1320 }
1321 }
1322 }
1323 }
1324
parseImport(tokenInfo * const token)1325 static void parseImport (tokenInfo *const token)
1326 {
1327 // ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
1328
1329 readToken (token);
1330 if (isType (token, TOKEN_EOF))
1331 return;
1332
1333 if (isType (token, TOKEN_OPEN_PAREN))
1334 {
1335 do
1336 {
1337 parseImportSpec (token);
1338 readToken (token);
1339 } while (!isType (token, TOKEN_EOF) &&
1340 !isType (token, TOKEN_CLOSE_PAREN));
1341 }
1342 else
1343 {
1344 parseImportSpec (token);
1345 return;
1346 }
1347 }
1348
parseGoFile(tokenInfo * const token)1349 static void parseGoFile (tokenInfo *const token)
1350 {
1351 int scope = CORK_NIL;
1352 do
1353 {
1354 readToken (token);
1355
1356 if (isType (token, TOKEN_KEYWORD))
1357 {
1358 switch (token->keyword)
1359 {
1360 case KEYWORD_package:
1361 scope = parsePackage (token);
1362 break;
1363 case KEYWORD_func:
1364 parseFunctionOrMethod (token, scope);
1365 break;
1366 case KEYWORD_const:
1367 parseConstTypeVar (token, GOTAG_CONST, scope);
1368 break;
1369 case KEYWORD_type:
1370 parseConstTypeVar (token, GOTAG_TYPE, scope);
1371 break;
1372 case KEYWORD_var:
1373 parseConstTypeVar (token, GOTAG_VAR, scope);
1374 break;
1375 case KEYWORD_import:
1376 parseImport (token);
1377 break;
1378 default:
1379 break;
1380 }
1381 }
1382 else if (isType (token, TOKEN_OPEN_PAREN) || isType (token, TOKEN_OPEN_CURLY) ||
1383 isType (token, TOKEN_OPEN_SQUARE))
1384 {
1385 skipToMatched (token, NULL);
1386 }
1387 } while (token->type != TOKEN_EOF);
1388 }
1389
findGoTags(void)1390 static void findGoTags (void)
1391 {
1392 tokenInfo *const token = newToken ();
1393
1394 parseGoFile (token);
1395
1396 deleteToken (token);
1397 }
1398
GoParser(void)1399 extern parserDefinition *GoParser (void)
1400 {
1401 static const char *const extensions[] = { "go", NULL };
1402 parserDefinition *def = parserNew ("Go");
1403 def->kindTable = GoKinds;
1404 def->kindCount = ARRAY_SIZE (GoKinds);
1405 def->extensions = extensions;
1406 def->parser = findGoTags;
1407 def->initialize = initialize;
1408 def->finalize = finalize;
1409 def->keywordTable = GoKeywordTable;
1410 def->keywordCount = ARRAY_SIZE (GoKeywordTable);
1411 def->fieldTable = GoFields;
1412 def->fieldCount = ARRAY_SIZE (GoFields);
1413 def->useCork = CORK_QUEUE | CORK_SYMTAB;
1414 def->requestAutomaticFQTag = true;
1415 return def;
1416 }
1417