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