xref: /Universal-ctags/parsers/perl-moose.c (revision d21b8e7920b2d1faec92e0409be1d8cf93b7d9fe)
1 /*
2  *   Copyright (c) 2019, Masatake YAMATO
3  *
4  *   This source code is released for free distribution under the terms of the
5  *   GNU General Public License version 2 or (at your option) any later version.
6  *
7  *   This module contains functions for generating tags for Moose perl extension.
8  *   https://metacpan.org/pod/Moose
9  *
10  *   This module can gather tags for Moo perl extension, too.
11  *   https://metacpan.org/pod/Moo
12  *
13  */
14 
15 /* NOTE about kind/role design:
16  *
17  * sub foo { ... }
18  * after 'foo' => sub { ...}
19  *
20  * There were two ideas to capture 'foo':
21  *
22  * A: capturing 'foo' as a reference tag with 'method' kind and 'wrapped' role, and
23  * B: capturing 'foo' as a definition tag with 'wrapper' kind.
24  *
25  * This implementation takes the idea B. */
26 
27 /*
28  *   INCLUDE FILES
29  */
30 #include "general.h"  /* must always come first */
31 
32 #include "debug.h"
33 #include "entry.h"
34 #include "kind.h"
35 #include "parse.h"
36 #include "perl.h"
37 #include "read.h"
38 #include "routines.h"
39 #include "trace.h"
40 
41 #include <string.h>
42 
43 /*
44  *   DATA DECLARATIONS
45  */
46 
47 enum MooseKind {
48 	K_CLASS,
49 	K_METHOD,
50 	K_ATTR,
51 	K_WRAPPER,
52 	K_ROLE,
53 };
54 
55 static kindDefinition MooseKinds[] = {
56 	{ true, 'c', "class", "classes"  },
57 	{ true, 'm', "method", "methods" },
58 	{ true, 'a', "attribute", "attributes" },
59 	{ true, 'w', "wrapper", "wrappers" },
60 	{ true, 'r', "role", "roles" },
61 };
62 
63 typedef enum {
64 	F_WRAPPING,
65 } MooseField;
66 
67 static fieldDefinition MooseFields [] = {
68 	{
69 		.name = "wrapping",
70 		.description = "how a wrapper wrapping the method (around, after, or before)",
71 		.enabled = true,
72 	},
73 };
74 
75 enum Wrapping {
76 	W_UNKNOWN,
77 	W_AROUND,
78 	W_BEFORE,
79 	W_AFTER,
80 };
81 
82 static char * WrappingStrings[] = {
83 	"unknown",
84 	"around",
85 	"before",
86 	"after",
87 };
88 
89 struct mooseSubparser {
90 	perlSubparser perl;
91 	bool notInMoose;
92 	bool inPod;
93 	int packageCork;
94 	int classCork;
95 	bool notContinuousExtendsLines;
96 
97 	int indexForFunctionParameters;
98 
99 	/* functionParametersModifiersStateCounter is for tracking the conditions
100 	 * that both "use Moose;" and "use Functions::Parameters qw/:modifiers/"
101 	 * are specified.
102 	 * functionParametersModifiersStateCounter is initialized to 2.
103 	 * Decrement functionParametersModifiersStateCounter when finding one of two.
104 	 * When functionParametersModifiersStateCounter is 0, the state is
105 	 * propagated to notInFunctionParametersModifiers. */
106 	bool notInFunctionParametersModifiers;
107 	int  functionParametersModifiersStateCounter;
108 #define RESET_FunctionParameters_STATE(MOOSE)				\
109 	do {													\
110 		MOOSE->notInFunctionParametersModifiers = true;		\
111 		MOOSE->functionParametersModifiersStateCounter = 2;	\
112 	} while (0)
113 #define DEC_FunctionParameters_STATE(MOOSE)							\
114 	do {															\
115 		MOOSE->functionParametersModifiersStateCounter--;			\
116 		if (MOOSE->functionParametersModifiersStateCounter == 0)	\
117 			MOOSE->notInFunctionParametersModifiers = false;		\
118 		if (MOOSE->functionParametersModifiersStateCounter < 0)		\
119 			MOOSE->functionParametersModifiersStateCounter = 0; 	\
120 	} while (0)
121 #define INC_FunctionParameters_STATE(MOOSE)						\
122 	do {														\
123 		MOOSE->functionParametersModifiersStateCounter++;		\
124 		if (MOOSE->functionParametersModifiersStateCounter > 0)	\
125 			MOOSE->notInFunctionParametersModifiers = true;		\
126 		if (MOOSE->functionParametersModifiersStateCounter > 2)	\
127 			MOOSE->functionParametersModifiersStateCounter = 2;	\
128 	} while (0)
129 
130 	vString *supersOrRoles;
131 };
132 
133 /*
134  *   FUNCTION PROTOTYPES
135  */
136 static void inputStart (subparser *s);
137 static void inputEnd (subparser *s);
138 static void makeTagEntryNotify (subparser *s, const tagEntryInfo *tag, int corkIndex);
139 static void enterMoose (struct mooseSubparser *moose, bool role);
140 static void leaveMoose (struct mooseSubparser *moose);
141 static void enteringPodNotify (perlSubparser *perl);
142 static void leavingPodNotify  (perlSubparser *perl);
143 static void findingQuotedWordNotify (perlSubparser *perl, int moduleIndex, const char *qwd);
144 
145 /*
146  *   DATA DEFINITIONS
147  */
148 
149 static struct mooseSubparser mooseSubparser = {
150 	.perl = {
151 		.subparser = {
152 			.direction  = SUBPARSER_BI_DIRECTION,
153 			.inputStart = inputStart,
154 			.inputEnd   = inputEnd,
155 			.makeTagEntryNotify = makeTagEntryNotify,
156 		},
157 		.enteringPodNotify = enteringPodNotify,
158 		.leavingPodNotify  = leavingPodNotify,
159 		.findingQuotedWordNotify = findingQuotedWordNotify,
160 	},
161 };
162 
163 
164 /*
165  *   FUNCTION DEFINITIONS
166  */
167 
inputStart(subparser * s)168 static void inputStart (subparser *s)
169 {
170 	struct mooseSubparser *moose = (struct mooseSubparser *)s;
171 
172 	moose->notInMoose = true;
173 	moose->packageCork = CORK_NIL;
174 	moose->classCork = CORK_NIL;
175 	moose->inPod = false;
176 	moose->supersOrRoles = vStringNew ();
177 	moose->notContinuousExtendsLines = true;
178 	moose->indexForFunctionParameters = CORK_NIL;
179 	RESET_FunctionParameters_STATE (moose);
180 }
181 
inputEnd(subparser * s)182 static void inputEnd (subparser *s)
183 {
184 	struct mooseSubparser *moose = (struct mooseSubparser *)s;
185 	tagEntryInfo *e = getEntryInCorkQueue (moose->classCork);
186 	if (e)
187 		e->extensionFields.endLine = getInputLineNumber ();
188 
189 	vStringDelete (moose->supersOrRoles);
190 	moose->supersOrRoles = NULL;
191 }
192 
makeTagEntryNotify(subparser * s,const tagEntryInfo * tag,int corkIndex)193 static void makeTagEntryNotify (subparser *s, const tagEntryInfo *tag, int corkIndex)
194 {
195 	perlSubparser *perl = (perlSubparser *)s;
196 	struct mooseSubparser *moose = (struct mooseSubparser *)perl;
197 
198 	if (isTagExtraBitMarked(tag, XTAG_QUALIFIED_TAGS))
199 		return;
200 
201 	if (tag->kindIndex == KIND_PERL_PACKAGE)
202 		moose->packageCork = corkIndex;
203 	else if ((!moose->notInMoose) && tag->kindIndex == KIND_PERL_SUBROUTINE)
204 	{
205 		tagEntryInfo moose_e;
206 		initTagEntry (&moose_e, tag->name, K_METHOD);
207 		setTagPositionFromTag (&moose_e, tag);
208 		moose_e.extensionFields.scopeIndex = moose->classCork;
209 		makeTagEntry (&moose_e);
210 	}
211 	else if (tag->kindIndex == KIND_PERL_MODULE)
212 	{
213 		if (isRoleAssigned(tag, ROLE_PERL_MODULE_USED))
214 		{
215 			if (strcmp (tag->name, "Moose") == 0
216 				|| strcmp (tag->name, "Moo") == 0)
217 				enterMoose (moose, false);
218 			else if (strcmp (tag->name, "Moose::Role") == 0)
219 				enterMoose (moose, true);
220 			else if (strcmp (tag->name, "Function::Parameters") == 0)
221 				moose->indexForFunctionParameters = corkIndex;
222 		}
223 		else if (isRoleAssigned(tag, ROLE_PERL_MODULE_UNUSED))
224 		{
225 			if (strcmp (tag->name, "Moose") == 0
226 				|| strcmp (tag->name, "Moo") == 0)
227 				leaveMoose (moose);
228 			else if (strcmp (tag->name, "Function::Parameters") == 0)
229 			{
230 				moose->indexForFunctionParameters = CORK_NIL;
231 				INC_FunctionParameters_STATE(moose);
232 			}
233 		}
234 	}
235 }
236 
enteringPodNotify(perlSubparser * perl)237 static void enteringPodNotify (perlSubparser *perl)
238 {
239 	struct mooseSubparser *moose = (struct mooseSubparser *)perl;
240 	moose->inPod = true;
241 }
242 
leavingPodNotify(perlSubparser * perl)243 static void leavingPodNotify  (perlSubparser *perl)
244 {
245 	struct mooseSubparser *moose = (struct mooseSubparser *)perl;
246 	moose->inPod = false;
247 }
248 
findingQuotedWordNotify(perlSubparser * perl,int moduleIndex,const char * qwd)249 static void findingQuotedWordNotify (perlSubparser *perl,
250 									 int moduleIndex, const char *qwd)
251 {
252 	struct mooseSubparser *moose = (struct mooseSubparser *)perl;
253 	if (moose->indexForFunctionParameters != moduleIndex)
254 		return;
255 
256 	if (strcmp (qwd, ":modifiers") == 0)
257 		DEC_FunctionParameters_STATE(moose);
258 }
259 
leaveMoose(struct mooseSubparser * moose)260 static void leaveMoose (struct mooseSubparser *moose)
261 {
262 	moose->notContinuousExtendsLines = true;
263 
264 	tagEntryInfo *e = getEntryInCorkQueue (moose->classCork);
265 	if (!e)
266 		return;
267 
268 	e->extensionFields.endLine = getInputLineNumber ();
269 
270 	moose->classCork = CORK_NIL;
271 	moose->notInMoose = true;
272 	moose->packageCork = CORK_NIL;
273 	INC_FunctionParameters_STATE(moose);
274 }
275 
enterMoose(struct mooseSubparser * moose,bool role)276 static void enterMoose (struct mooseSubparser *moose, bool role)
277 {
278 	moose->notContinuousExtendsLines = true;
279 
280 	tagEntryInfo *perl_e = getEntryInCorkQueue (moose->packageCork);
281 	if (!perl_e)
282 		return;
283 
284 	moose->notInMoose = false;
285 
286 	tagEntryInfo moose_e;
287 	initTagEntry (&moose_e, perl_e->name, role? K_ROLE: K_CLASS);
288 	moose_e.lineNumber = perl_e->lineNumber;
289 	moose_e.filePosition = perl_e->filePosition;
290 	moose->classCork = makeTagEntry (&moose_e);
291 	vStringClear (moose->supersOrRoles);
292 
293 	DEC_FunctionParameters_STATE(moose);
294 
295 	return;
296 }
297 
parseExtendsClass(const char * input,size_t input_len,vString * inherits,bool * notContinuousLine)298 static void parseExtendsClass (const char *input,
299 							   size_t input_len,
300 							   vString *inherits,
301 							   bool *notContinuousLine)
302 {
303 	int i = 0;
304 	do
305 	{
306 		if (input [i] == ',')
307 			i++;
308 
309 		for (; (i < input_len) && input[i] != '\n' && input[i] != '\0'; i++)
310 		{
311 			if (input[i] == '\'' || input[i] == ' '  || input[i] == '\t')
312 				continue;
313 			else if (input[i] == ',')
314 			{
315 				vStringPut (inherits, ',');
316 				*notContinuousLine = false;
317 				break;
318 			}
319 			else if (input[i] == ';')
320 			{
321 				*notContinuousLine = true;
322 				break;
323 			}
324 			else
325 				vStringPut (inherits, input[i]);
326 		}
327 	}
328 	while (input[i] == ',');
329 }
330 
findExtendsClass(const char * line,const regexMatch * matches,unsigned int count,void * data)331 static bool findExtendsClass (const char *line,
332 							  const regexMatch *matches,
333 							  unsigned int count,
334 							  void *data)
335 {
336 	struct mooseSubparser *moose = data;
337 	moose->notContinuousExtendsLines = true;
338 
339 	if (moose->inPod)
340 		return true;
341 
342 	tagEntryInfo *e = getEntryInCorkQueue (moose->classCork);
343 	if (!e)
344 		return true;
345 
346 	const char *input = line + matches[2].start;
347 	vString *str = moose->supersOrRoles;
348 
349 	if (vStringLength (str) > 0)
350 		vStringPut (str, ',');
351 	parseExtendsClass (input, matches[2].length, str,
352 					   &moose->notContinuousExtendsLines);
353 
354 	if (moose->notContinuousExtendsLines == true
355 		&& vStringLength (str) > 0)
356 	{
357 		if (e->extensionFields.inheritance)
358 			eFree ((void *)e->extensionFields.inheritance);
359 		e->extensionFields.inheritance = vStringStrdup (str);
360 	}
361 
362 	return true;
363 }
364 
findExtendsClassContinuation(const char * line,const regexMatch * matches,unsigned int count,void * data)365 static bool findExtendsClassContinuation (const char *line,
366 										  const regexMatch *matches,
367 										  unsigned int count,
368 										  void *data)
369 {
370 	struct mooseSubparser *moose = data;
371 	moose->notContinuousExtendsLines = true;
372 
373 	tagEntryInfo *e = getEntryInCorkQueue (moose->classCork);
374 	if (!e)
375 		return true;
376 
377 	const char *input = line + matches[1].start;
378 	vString *str = moose->supersOrRoles;
379 
380 	parseExtendsClass (input, matches[1].length, str,
381 					   &moose->notContinuousExtendsLines);
382 
383 	if (moose->notContinuousExtendsLines == true
384 		&& vStringLength (str) > 0)
385 	{
386 		if (e->extensionFields.inheritance)
387 			eFree ((void *)e->extensionFields.inheritance);
388 		e->extensionFields.inheritance = vStringStrdup (str);
389 	}
390 
391 	return true;
392 }
393 
parseAttributeOrWrapper(const char * str,int parentCorkIndex,int extraTerminator,int kind,enum Wrapping wrapping)394 static const char *parseAttributeOrWrapper (const char *str, int parentCorkIndex, int extraTerminator,
395 											int kind, enum Wrapping wrapping)
396 {
397 	int i;
398 
399 	for (i = 0;
400 		 str[i]
401 			 && str[i] != extraTerminator
402 			 && (isalnum ((unsigned char)str[i]) || str[i] == '_');
403 		 i++)
404 		;						/* Just advancing `i' */
405 
406 	if (i == 0)
407 		return NULL;
408 
409 	tagEntryInfo e;
410 	char *buf = eStrndup (str, i);
411 
412 	initTagEntry (&e, buf, kind);
413 	if (parentCorkIndex != CORK_NIL)
414 		e.extensionFields.scopeIndex = parentCorkIndex;
415 
416 	int corkIndex = makeTagEntry (&e);
417 	if (kind == K_WRAPPER)
418 		attachParserFieldToCorkEntry (corkIndex, MooseFields[F_WRAPPING].ftype,
419 									  WrappingStrings [wrapping]);
420 	eFree (buf);
421 
422 	return str[i] == '\0'? NULL: str + i;
423 }
424 
solveKindAndWrapping(const char * str,int * kind,enum Wrapping * wrapping)425 static void solveKindAndWrapping (const char *str, int *kind, enum Wrapping *wrapping)
426 {
427 	*kind = K_WRAPPER;
428 	*wrapping = W_UNKNOWN;
429 	switch (str[0])
430 	{
431 	case 'h':					/* has */
432 		*kind = K_ATTR;
433 		break;
434 	case 'a':					/* around or after */
435 		if (str[1] == 'r')
436 			*wrapping = W_AROUND;
437 		else if (str[1] == 'f')
438 			*wrapping = W_AFTER;
439 		break;
440 	case 'b':					/* before */
441 		*wrapping = W_BEFORE;
442 		break;
443 	case 'o':					/* override */
444 		*kind = K_METHOD;
445 	}
446 }
447 
findAttributeOrWrapperOne(const char * line,const regexMatch * matches,unsigned int count,void * data)448 static bool findAttributeOrWrapperOne (const char *line,
449 									   const regexMatch *matches,
450 									   unsigned int count,
451 									   void *data)
452 {
453 	struct mooseSubparser *moose = data;
454 	int kind;
455 	enum Wrapping wrapping;
456 
457 	if (moose->inPod)
458 		return true;
459 
460 	moose->notContinuousExtendsLines = true;
461 
462 	if (count < 3)
463 		return true;
464 
465 	solveKindAndWrapping (line + matches[1].start, &kind, &wrapping);
466 	parseAttributeOrWrapper (line + matches[2].start, moose->classCork, -1,
467 							 kind, wrapping);
468 	return true;
469 }
470 
findAttributeOrWrapperMulti(const char * line,const regexMatch * matches,unsigned int count,void * data)471 static bool findAttributeOrWrapperMulti (const char *line,
472 										 const regexMatch *matches,
473 										 unsigned int count,
474 										 void *data)
475 {
476 
477 	struct mooseSubparser *moose = data;
478 	int kind;
479 	enum Wrapping wrapping;
480 	int terminator;
481 
482 	if (moose->inPod)
483 		return true;
484 
485 	moose->notContinuousExtendsLines = true;
486 
487 	if (count < 4)
488 		return true;
489 
490 	solveKindAndWrapping (line + matches[1].start, &kind, &wrapping);
491 	terminator = line[matches[2].start];
492 
493 
494 	const char *str = line + matches[3].start;
495 	while ((str = parseAttributeOrWrapper (str, moose->classCork, terminator,
496 										   kind, wrapping)))
497 	{
498 		int i;
499 		for (i = 0; (str[i] == ' ') || (str[i] == '\t'); i++)
500 			;
501 		if (str[i] == '\0' || str[i] == terminator)
502 			break;
503 		str = str + i;
504 	}
505 
506 	return true;
507 }
508 
findMooseTags(void)509 static void findMooseTags (void)
510 {
511 	scheduleRunningBaseparser (RUN_DEFAULT_SUBPARSERS);
512 }
513 
initializeMooseParser(langType language)514 static void initializeMooseParser (langType language)
515 {
516 	addLanguageCallbackRegex (language, "^[ \t]*(extends|with) *(.+)",
517 							  "{exclusive}",
518 							  findExtendsClass, &mooseSubparser.notInMoose,
519 							  &mooseSubparser);
520 	addLanguageCallbackRegex (language, "^[ \t]*(has|after|before|around|override) +"
521 							  /* foo => ()
522 							   * 'foo' => ()
523 							   * "foo" => ()
524 							   * ( foo => ())
525 							   * ( "foo" => ())
526 							   * ( 'foo' => ()) */
527 							  "\\(?[ \t]*[\"']?"
528 							  ""
529 							  "([a-zA-Z_][a-zA-Z0-9_]*([ \t][a-zA-Z_][a-zA-Z0-9_]*)*).*=>",
530 							  "{exclusive}",
531 							  findAttributeOrWrapperOne, &mooseSubparser.notInMoose,
532 							  &mooseSubparser);
533 	addLanguageCallbackRegex (language, "^[ \t]*(has|after|before|around) +\\(?\\[qw([^ \t])[ \t]*"
534 							  /* [qw/foo bar/] => ()
535 							   * ([qw/foo bar/] => ()) */
536 							  "([a-zA-Z_][a-zA-Z0-9_]*([ \t][a-zA-Z_][a-zA-Z0-9_]*)*).*=>",
537 							  "{exclusive}",
538 							  findAttributeOrWrapperMulti, &mooseSubparser.notInMoose,
539 							  &mooseSubparser);
540 	addLanguageCallbackRegex (language, "^[ \t]*(has|after|before|around|override) +"
541 							  "([a-zA-Z_][a-zA-Z0-9_]*)[ \t]*\\(",
542 							  "{exclusive}",
543 							  findAttributeOrWrapperOne, &mooseSubparser.notInFunctionParametersModifiers,
544 							  &mooseSubparser);
545 	addLanguageCallbackRegex (language, "^[ \t]*(.+)",
546 							  "{exclusive}",
547 							  findExtendsClassContinuation, &mooseSubparser.notContinuousExtendsLines,
548 							  &mooseSubparser);
549 }
550 
MooseParser(void)551 extern parserDefinition* MooseParser (void)
552 {
553 	parserDefinition* const def = parserNew("Moose");
554 
555 	static parserDependency dependencies [] = {
556 		[0] = { DEPTYPE_SUBPARSER, "Perl", &mooseSubparser },
557 	};
558 
559 	def->dependencies = dependencies;
560 	def->dependencyCount = ARRAY_SIZE (dependencies);
561 
562 	def->kindTable = MooseKinds;
563 	def->kindCount = ARRAY_SIZE(MooseKinds);
564 
565 	def->fieldTable = MooseFields;
566 	def->fieldCount = ARRAY_SIZE (MooseFields);
567 
568 	def->initialize = initializeMooseParser;
569 	def->parser = findMooseTags;
570 	def->useCork = CORK_QUEUE;
571 
572 	return def;
573 }
574