xref: /Universal-ctags/parsers/fypp.c (revision 55919c582e891b1de6ab4d5ac7e8c124dbcde842)
1 /*
2  *   Copyright (c) 2018 Masatake YAMATO
3  *   Copyright (c) 2018 Red Hat, Inc.
4  *
5  *   This source code is released for free distribution under the terms of the
6  *   GNU General Public License version 2 or (at your option) any later version.
7  *
8  *   This module contains functions to generate tags for Fypp, a Python powered
9  *   Fortran preprocessor.
10  *   See: https://github.com/aradi/fypp
11  */
12 
13 #include "general.h"  /* must always come first */
14 
15 #include "ctags.h"
16 #include "debug.h"
17 #include "entry.h"
18 #include "numarray.h"
19 #include "param.h"
20 #include "parse.h"
21 #include "promise.h"
22 #include "read.h"
23 #include "trace.h"
24 
25 #include <string.h>
26 
27 typedef enum {
28 	K_MACRO
29 } fyppKind;
30 
31 static kindDefinition FyppKinds[] = {
32 	{ true, 'm', "macro", "macros" },
33 };
34 
35 /* TODO
36    If "guest" extra is disabled, we can reduce the codes to be processed. */
37 
38 struct fyppParseCtx {
39 	int macro_cork_index;
40 
41 	/* Areas surrounded by Fypp directive should be masked with white
42 	   spaces when running a guest parser like Fortran parser.
43 
44 	   in_fypp_area field counts the depth of areas. Zero means the
45 	   current line is not part of a Fypp area; the line should passed
46 	   as is to the guest parser. Otherwise it should not.
47 
48 	   #:if/#:elif/#:else/#:endif areas are handled in a special way.
49 	   #:if areas are passed to the guest parser.
50 	   #:elif/#:else/#:endif areas are not.
51 	   This approach follows what CPreProcessor parser does.
52 	   Quoted from ctags.1 man page:
53 
54 	   If a preprocessor conditional is encountered within a statement
55 	   which defines a tag, ctags follows only the first branch of
56 	   that conditional (except in the special case of "#if 0", in
57 	   which case it follows only the last branch).
58 
59 	   About the behavior about "#if 0" is not applicable to Fypp parser.
60 	   if_tracker is for tracking "the first branch of that conditional".
61 	   A scalar value is not enough to track the branch because branches
62 	   can be nested:
63 
64 	   #:if COND0
65 	   ***
66 	   #:if COND1
67 	   ***
68 	   #:else
69 	   ...
70 	   #:endif
71 	   ...
72 	   #:else
73 	   ...
74 	   #:endif
75 
76 	   In the above example, Fypp parser passes only `***` lines to the
77 	   guest parser.
78 
79 	   if_tracker field tracks the branches. When the parser enters #:if area,
80 	   it appends 1 to if_tracker. When it enters #:elif or #:else area,
81 	   it repalces 1 with 0. When it reaches at #:endif, remove the last
82 	   element from the field. The product of if_tracker should be either
83 	   0 or 1. In the case that the product is 1, we can say the current line
84 	   is in the first if block.
85 
86 	   if_cont field is for tracking '&' at the end of #:if directive lines.
87 	   If it is true, the last line is ended with &. We can know the
88 	   current line is part of Fypp directive that should not be
89 	   passed to the guest parser.
90 
91 	   other_cont field has the same purpose as if_cont but
92 	   other_cont is for the other directives than #:if.
93 	*/
94 	int in_fypp_area;
95 	intArray *if_tracker;
96 	bool if_cont;
97 	bool other_cont;
98 
99 	/* fypp_lines field records the line numbers for masking. */
100 	ulongArray *fypp_lines;
101 };
102 
103 static vString *fyppGuestParser;
104 
fypp_does_line_continue(const char * line,const regexMatch * matches)105 static bool fypp_does_line_continue(const char *line,
106 									const regexMatch *matches)
107 {
108 	if (matches[0].length == 0)
109 		return false;
110 	return *(line + matches[0].start + matches[0].length - 1) == '&';
111 }
112 
fypp_start_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)113 static bool fypp_start_cb(const char *line,
114 						  const regexMatch *matches,
115 						  unsigned int count,
116 						  void *userData)
117 {
118 	struct fyppParseCtx *ctx = userData;
119 
120 	TRACE_PRINT_PREFIX();
121 	TRACE_PRINT_FMT("%04d - %s", getInputLineNumber(), line);
122 
123 	ulongArrayAdd (ctx->fypp_lines,
124 				   getInputLineNumber());
125 	ctx->in_fypp_area++;
126 	ctx->if_cont = false;
127 	ctx->other_cont = false;
128 
129 	return true;
130 }
131 
fypp_end_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)132 static bool fypp_end_cb(const char *line,
133 						const regexMatch *matches,
134 						unsigned int count,
135 						void *userData)
136 {
137 	struct fyppParseCtx *ctx = userData;
138 
139 	TRACE_PRINT_PREFIX();
140 	TRACE_PRINT_FMT("%04d - %s", getInputLineNumber(), line);
141 
142 	ulongArrayAdd (ctx->fypp_lines,
143 				   getInputLineNumber());
144 	ctx->in_fypp_area--;
145 	ctx->if_cont = false;
146 	ctx->other_cont = false;
147 
148 	return true;
149 }
150 
fypp_line_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)151 static bool fypp_line_cb (const char *line,
152 						  const regexMatch *matches,
153 						  unsigned int count,
154 						  void *userData)
155 {
156 	struct fyppParseCtx *ctx = userData;
157 
158 	TRACE_PRINT_PREFIX();
159 	TRACE_PRINT_FMT("%04d - %s", getInputLineNumber(), line);
160 
161 	ulongArrayAdd (ctx->fypp_lines,
162 				   getInputLineNumber());
163 	ctx->if_cont = false;
164 	ctx->other_cont = fypp_does_line_continue (line, matches);
165 
166 	return true;
167 }
168 
macro_start_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)169 static bool macro_start_cb (const char *line,
170 							const regexMatch *matches,
171 							unsigned int count,
172 							void *userData)
173 {
174 	struct fyppParseCtx *ctx = userData;
175 	vString *macro = NULL;
176 	vString *signature = NULL;
177 
178 	if (count > 0)
179 	{
180 		tagEntryInfo e;
181 
182 		macro = vStringNew ();
183 		vStringNCopyS (macro,
184 					   line + matches[1].start,
185 					   matches[1].length);
186 
187 		initTagEntry (&e, vStringValue (macro), K_MACRO);
188 
189 		if (count > 1)
190 		{
191 			signature = vStringNew ();
192 			vStringNCopyS (signature,
193 						   line + matches[2].start,
194 						   matches[2].length);
195 			e.extensionFields.signature = vStringValue (signature);
196 		}
197 
198 		ctx->macro_cork_index = makeTagEntry (&e);
199 	}
200 
201 	if (macro)
202 		vStringDelete (macro);
203 	if (signature)
204 		vStringDelete (signature);
205 
206 	fypp_start_cb (line, matches, count, userData);
207 	return true;
208 }
209 
macro_end_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)210 static bool macro_end_cb (const char *line,
211 						  const regexMatch *matches,
212 						  unsigned int count,
213 						  void *userData)
214 {
215 	struct fyppParseCtx *ctx = userData;
216 
217 	tagEntryInfo *e = getEntryInCorkQueue (ctx->macro_cork_index);
218 	if (e)
219 		e->extensionFields.endLine = getInputLineNumber ();
220 	ctx->macro_cork_index = CORK_NIL;
221 
222 	fypp_end_cb (line, matches, count, userData);
223 	return true;
224 }
225 
if_start_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)226 static bool if_start_cb (const char *line,
227 						 const regexMatch *matches,
228 						 unsigned int count,
229 						 void *userData)
230 {
231 	struct fyppParseCtx *ctx = userData;
232 
233 	if (ctx->if_tracker == NULL)
234 		ctx->if_tracker = intArrayNew ();
235 
236 	intArrayAdd (ctx->if_tracker, 1);
237 
238 	fypp_start_cb (line, matches, count, userData);
239 
240 	ctx->if_cont = fypp_does_line_continue (line, matches);
241 	TRACE_PRINT("(count: %d, len: %d, ifCont: %d)",
242 				count, matches[1].length, ctx->if_cont);
243 
244 	return true;
245 }
246 
if_else_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)247 static bool if_else_cb (const char *line,
248 						const regexMatch *matches,
249 						unsigned int count,
250 						void *userData)
251 {
252 	struct fyppParseCtx *ctx = userData;
253 
254 	if (ctx->if_tracker)
255 	{
256 		intArrayRemoveLast (ctx->if_tracker);
257 		intArrayAdd (ctx->if_tracker, 0);
258 	}
259 
260 	fypp_line_cb(line, matches, count, userData);
261 	return true;
262 }
263 
if_end_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)264 static bool if_end_cb (const char *line,
265 					   const regexMatch *matches,
266 					   unsigned int count,
267 					   void *userData)
268 {
269 	struct fyppParseCtx *ctx = userData;
270 
271 	if (ctx->if_tracker)
272 	{
273 		intArrayRemoveLast (ctx->if_tracker);
274 
275 		if (intArrayCount(ctx->if_tracker) == 0)
276 		{
277 			intArrayDelete (ctx->if_tracker);
278 			ctx->if_tracker = NULL;
279 		}
280 	}
281 
282 	fypp_end_cb (line, matches, count, userData);
283 	return true;
284 }
285 
is_in_first_if_block(struct fyppParseCtx * ctx)286 static bool is_in_first_if_block (struct fyppParseCtx *ctx)
287 {
288 	int r = 1;
289 
290 	for (unsigned int i = 0; i < intArrayCount (ctx->if_tracker); i++)
291 		r *= intArrayItem(ctx->if_tracker, i);
292 
293 	return r;
294 }
295 
non_fypp_line_cb(const char * line,const regexMatch * matches,unsigned int count,void * userData)296 static bool non_fypp_line_cb (const char *line,
297 							  const regexMatch *matches,
298 							  unsigned int count,
299 							  void *userData)
300 {
301 	struct fyppParseCtx *ctx = userData;
302 
303 	if ((ctx->in_fypp_area > 0 && ((!ctx->if_tracker)
304 								  || (! is_in_first_if_block (ctx))
305 								  || ctx->if_cont))
306 		|| ctx->other_cont )
307 	{
308 		ulongArrayAdd (ctx->fypp_lines,
309 					   getInputLineNumber());
310 
311 		TRACE_PRINT_PREFIX();
312 		TRACE_PRINT_FMT("%04d - %s", getInputLineNumber(), line);
313 		TRACE_PRINT_PREFIX();
314 		TRACE_PRINT_FMT("(inFyppArea: %d, ifTracker: %p, inFirstIfBlock: %d, ifCont: %d otherCont: %d/ ",
315 						ctx->in_fypp_area, ctx->if_tracker,
316 						ctx->if_tracker? is_in_first_if_block (ctx): -1,
317 						ctx->if_cont,
318 						ctx->other_cont);
319 #ifdef DO_TRACING
320 		if (ctx->if_tracker)
321 		{
322 			for (int i = 0; i < intArrayCount (ctx->if_tracker); i++)
323 				TRACE_PRINT_FMT("%d ", intArrayItem(ctx->if_tracker, i));
324 		}
325 #endif	/* DO_TRACING */
326 		TRACE_PRINT_FMT(")");
327 		TRACE_PRINT_NEWLINE();
328 
329 		bool continued = fypp_does_line_continue(line, matches);
330 		if (ctx->if_cont)
331 		{
332 			ctx->if_cont = continued;
333 			TRACE_PRINT("(ifCont <= %d)", ctx->if_cont);
334 		}
335 		if (ctx->other_cont)
336 		{
337 			ctx->other_cont = continued;
338 			TRACE_PRINT("(otherCont <= %d)", ctx->other_cont);
339 		}
340 	}
341 	else
342 	{
343 		TRACE_PRINT_PREFIX();
344 		TRACE_PRINT_FMT("%04d + %s", getInputLineNumber(), line);
345 		TRACE_PRINT_PREFIX();
346 		TRACE_PRINT_FMT("(inFyppArea: %d, ifTracker: %p, ifCont: %d / ",
347 						ctx->in_fypp_area, ctx->if_tracker,
348 						ctx->if_cont);
349 		TRACE_PRINT_FMT(")");
350 		TRACE_PRINT_NEWLINE();
351 	}
352 
353 	return true;
354 }
355 
356 
357 static struct fyppParseCtx parseCtx;
358 
findFyppTags(void)359 static void findFyppTags (void)
360 {
361 	int promise;
362 
363 	parseCtx.macro_cork_index = CORK_NIL;
364 	parseCtx.if_tracker = NULL;
365 
366 	if (fyppGuestParser)
367 		parseCtx.fypp_lines = ulongArrayNew ();
368 
369 	findRegexTags ();
370 
371 
372 	if (fyppGuestParser)
373 	{
374 		promise = makePromise (vStringValue(fyppGuestParser),
375 							   1, 0,
376 							   getInputLineNumber(), 0,
377 							   0);
378 		if (promise >= 0)
379 			promiseAttachLineFiller (promise, parseCtx.fypp_lines);
380 		else
381 			ulongArrayDelete (parseCtx.fypp_lines);
382 	}
383 
384 	if (parseCtx.if_tracker)
385 	{
386 		intArrayDelete (parseCtx.if_tracker);
387 		parseCtx.if_tracker = NULL;
388 	}
389 }
390 
initializeFyppParser(langType language)391 static void initializeFyppParser (langType language)
392 {
393 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*def[ \t]*([a-zA-Z][a-zA-Z0-9_]*)[ \t]*(\\(.*\\))",
394 							  "{exclusive}", macro_start_cb, NULL, &parseCtx);
395 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*enddef[ \t]*([a-zA-Z][a-zA-Z0-9_]*)[ \t]*$",
396 							  "{exclusive}", macro_end_cb, NULL, &parseCtx);
397 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*for[ \t].*$",
398 							  "{exclusive}", fypp_start_cb, NULL, &parseCtx);
399 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*endfor.*$",
400 							  "{exclusive}", fypp_end_cb, NULL, &parseCtx);
401 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*call[ \t].*$",
402 							  "{exclusive}", fypp_start_cb, NULL, &parseCtx);
403 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*endcall.*$",
404 							  "{exclusive}", fypp_end_cb, NULL, &parseCtx);
405 
406 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*if[ \t].*$",
407 							  "{exclusive}", if_start_cb, NULL, &parseCtx);
408 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*el(se|if)[ \t].*$",
409 							  "{exclusive}", if_else_cb, NULL, &parseCtx);
410 	addLanguageCallbackRegex (language,  "^[ \t]*#:[ \t]*endif.*",
411 							  "{exclusive}", if_end_cb, NULL, &parseCtx);
412 
413 	addLanguageCallbackRegex (language,  "^[ \t]*(#!|[#@$]:).*$",
414 							  "{exclusive}", fypp_line_cb, NULL, &parseCtx);
415 	addLanguageCallbackRegex (language,  "^.*$",
416 							  "{exclusive}", non_fypp_line_cb, NULL, &parseCtx);
417 }
418 
finalizeFyppParser(langType language,bool initialized)419 static void finalizeFyppParser (langType language, bool initialized)
420 {
421 	if (fyppGuestParser)
422 	{
423 		vStringDelete (fyppGuestParser);
424 		fyppGuestParser = NULL;
425 	}
426 }
427 
fyppSetGuestParser(const langType language CTAGS_ATTR_UNUSED,const char * optname CTAGS_ATTR_UNUSED,const char * arg)428 static void fyppSetGuestParser (const langType language CTAGS_ATTR_UNUSED,
429 								const char *optname CTAGS_ATTR_UNUSED, const char *arg)
430 {
431 	if (!strcmp (arg, RSV_NONE))
432 	{
433 		if (fyppGuestParser)
434 		{
435 			vStringDelete (fyppGuestParser);
436 			fyppGuestParser = NULL;
437 		}
438 		return;
439 	}
440 
441 	langType lang = getNamedLanguage (arg, strlen(arg));
442 	if (lang == LANG_IGNORE)
443 		error (FATAL, "Unknown language: %s", arg);
444 
445 	if (fyppGuestParser)
446 		vStringClear(fyppGuestParser);
447 	else
448 		fyppGuestParser = vStringNew();
449 	vStringCatS (fyppGuestParser, arg);
450 }
451 
452 static parameterHandlerTable FyppParameterHandlerTable [] = {
453 	{
454 		.name = "guest",
455 		.desc = "parser run after Fypp parser parses the original input (\"NONE\" or a parser name [Fortran])" ,
456 		.handleParameter = fyppSetGuestParser,
457 	},
458 };
459 
FyppParser(void)460 extern parserDefinition* FyppParser (void)
461 {
462 	static const char *const extensions [] = { "fy", NULL };
463 	parserDefinition* const def = parserNew ("Fypp");
464 	def->kindTable = FyppKinds;
465 	def->kindCount = ARRAY_SIZE (FyppKinds);
466 	def->extensions = extensions;
467 	def->parser = findFyppTags;
468 	def->initialize = initializeFyppParser;
469 	def->finalize   = finalizeFyppParser;
470 	def->method     = METHOD_REGEX;
471 
472 	def->parameterHandlerTable = FyppParameterHandlerTable;
473 	def->parameterHandlerCount = ARRAY_SIZE(FyppParameterHandlerTable);
474 
475 	def->useCork = CORK_QUEUE;
476 
477 	fyppGuestParser = vStringNewInit ("Fortran");
478 
479 	return def;
480 }
481