1 /*
2 * Copyright (c) 2022 Masatake YAMATO <yamato@redhat.com>
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 Rakefile.
8 *
9 * Reference:
10 * - https://ruby.github.io/rake/doc/rakefile_rdoc.html
11 */
12
13 /*
14 * INCLUDE FILES
15 */
16 #include "general.h" /* must always come first */
17
18 #include "entry.h"
19 #include "kind.h"
20 #include "numarray.h"
21 #include "parse.h"
22 #include "subparser.h"
23
24 #include "ruby.h"
25
26 #include <string.h>
27
28 /*
29 * DATA STRUCTURES
30 */
31 struct sRakeSubparser {
32 rubySubparser ruby;
33 intArray *namespaces;
34 };
35
36 typedef enum {
37 K_TASK,
38 K_NAMESPACE,
39 K_FILE,
40 K_DIRECTORY,
41 K_MULTITASK,
42 K_XTASK,
43 } rakeKind;
44
45 /*
46 * DATA DEFINITIONS
47 */
48 static scopeSeparator RakeGenericSeparators [] = {
49 { KIND_WILDCARD_INDEX, ":" },
50 };
51
52 static kindDefinition RakeKinds [] = {
53 { true, 't', "task", "tasks",
54 ATTACH_SEPARATORS(RakeGenericSeparators)},
55 { true, 'n', "namespace", "namespaces",
56 ATTACH_SEPARATORS(RakeGenericSeparators)},
57 /* F/file is reserved in the main part. */
58 { true, 'f', "File", "file tasks",
59 ATTACH_SEPARATORS(RakeGenericSeparators)},
60 { true, 'd', "directory", "directory tasks",
61 ATTACH_SEPARATORS(RakeGenericSeparators)},
62 { true, 'm', "multitask", "multi tasks",
63 ATTACH_SEPARATORS(RakeGenericSeparators)},
64 { true, 'x', "xtask", "tasks defined with special constructor",
65 ATTACH_SEPARATORS(RakeGenericSeparators)},
66 };
67
68 /*
69 * FUNCTIONS
70 */
findRakeTags(void)71 static void findRakeTags (void)
72 {
73 scheduleRunningBaseparser (0);
74 }
75
readTask(const unsigned char ** cp,bool * variable)76 static vString *readTask (const unsigned char **cp, bool *variable)
77 {
78 vString *vstr = NULL;
79 unsigned char b;
80 const unsigned char *start;
81
82 switch (**cp)
83 {
84 case '\'':
85 case '"':
86 b = **cp;
87 ++*cp;
88 vstr = vStringNew ();
89 if (!rubyParseString (cp, b, vstr))
90 {
91 vStringDelete (vstr);
92 vstr = NULL;
93 }
94 break;
95 case ':':
96 ++*cp;
97 vstr = vStringNew ();
98 if (!rubyParseMethodName (cp, vstr))
99 {
100 vStringDelete (vstr);
101 vstr = NULL;
102 }
103 break;
104 default:
105 vstr = vStringNew ();
106 start = *cp;
107 if (!rubyParseMethodName (cp, vstr))
108 {
109 vStringDelete (vstr);
110 vstr = NULL;
111 }
112 else
113 {
114 const char *end = strstr((const char *)start, vStringValue (vstr));
115 if (end)
116 {
117 end += vStringLength (vstr);
118 if (*end != ':')
119 *variable = true;
120 }
121 }
122 break;
123 }
124 return vstr;
125 }
126
makeSimpleRakeTag(vString * vstr,int kindIndex,rubySubparser * subparser,bool anonymous)127 static int makeSimpleRakeTag (vString *vstr, int kindIndex, rubySubparser *subparser,
128 bool anonymous)
129 {
130 if (anonymous)
131 {
132 vStringPut (vstr, '_');
133 anonConcat (vstr, kindIndex);
134 }
135
136 int r = makeSimpleTag (vstr, kindIndex);
137 tagEntryInfo *e = getEntryInCorkQueue (r);
138 if (e)
139 {
140 struct sRakeSubparser *rake = (struct sRakeSubparser *)subparser;
141 if (!intArrayIsEmpty (rake->namespaces))
142 e->extensionFields.scopeIndex = intArrayLast(rake->namespaces);
143 if (anonymous)
144 markTagExtraBit (e, XTAG_ANONYMOUS);
145 }
146 return r;
147 }
148
149 struct taskType {
150 const char *keyword;
151 rakeKind kind;
152 };
153
parseTask(rubySubparser * s,int kind,const unsigned char ** cp)154 static int parseTask (rubySubparser *s, int kind, const unsigned char **cp)
155 {
156 vString *vstr = NULL;
157 bool variable = false;
158 rubySkipWhitespace (cp);
159 vstr = readTask (cp, &variable);
160 if (vstr)
161 {
162 int r = makeSimpleRakeTag (vstr, kind, s, variable);
163 vStringDelete (vstr);
164 return r;
165 }
166 return CORK_NIL;
167 }
168
parseXTask(rubySubparser * s,struct taskType * xtask,const unsigned char ** cp)169 static int parseXTask (rubySubparser *s, struct taskType *xtask, const unsigned char **cp)
170 {
171 rubySkipWhitespace (cp);
172 if (**cp == '(')
173 {
174 vString *vstr = NULL;
175 bool variable = false;
176 ++*cp;
177 rubySkipWhitespace (cp);
178 vstr = readTask (cp, &variable);
179 if (vstr)
180 {
181 int r = makeSimpleRakeTag (vstr, xtask->kind, s, variable);
182 vStringDelete (vstr);
183 tagEntryInfo *e = getEntryInCorkQueue (r);
184 e->extensionFields.typeRef [0] = eStrdup ("typename");
185 e->extensionFields.typeRef [1] = eStrdup (xtask->keyword);
186 return r;
187 }
188 }
189 return CORK_NIL;
190 }
191
lineNotify(rubySubparser * s,const unsigned char ** cp)192 static int lineNotify (rubySubparser *s, const unsigned char **cp)
193 {
194 struct taskType taskTypes [] = {
195 { "task", K_TASK },
196 { "namespace", K_NAMESPACE },
197 { "file", K_FILE },
198 { "directory", K_DIRECTORY },
199 { "multitask", K_MULTITASK },
200 };
201
202 for (int i = 0; i < ARRAY_SIZE(taskTypes); i++)
203 {
204 if (rubyCanMatchKeywordWithAssign (cp, taskTypes[i].keyword))
205 return parseTask (s, taskTypes[i].kind, cp);
206 }
207
208 struct taskType xtaskTypes [] = {
209 { "RSpec::Core::RakeTask.new", K_XTASK },
210 { "Cucumber::Rake::Task.new", K_XTASK },
211 { "Rake::TestTask.new", K_XTASK },
212 { "Rake::PackageTask.new", K_XTASK },
213 /* ... */
214 };
215
216 for (int i = 0; i < ARRAY_SIZE(xtaskTypes); i++)
217 {
218 if (rubyCanMatchKeywordWithAssign (cp, xtaskTypes[i].keyword))
219 return parseXTask (s, xtaskTypes + i, cp);;
220 }
221
222 return CORK_NIL;
223 }
224
inputStart(subparser * s)225 static void inputStart (subparser *s)
226 {
227 struct sRakeSubparser *rake = (struct sRakeSubparser *)s;
228
229 intArrayClear (rake->namespaces);
230 }
231
enterBlockNotify(rubySubparser * s,int corkIndex)232 static void enterBlockNotify (rubySubparser *s, int corkIndex)
233 {
234 tagEntryInfo *e = getEntryInCorkQueue (corkIndex);
235
236 if (e && e->kindIndex == K_NAMESPACE)
237 {
238 struct sRakeSubparser *rake = (struct sRakeSubparser *)s;
239 intArrayAdd (rake->namespaces, corkIndex);
240 }
241 }
242
leaveBlockNotify(rubySubparser * s,int corkIndex)243 static void leaveBlockNotify (rubySubparser *s, int corkIndex)
244 {
245 tagEntryInfo *e = getEntryInCorkQueue (corkIndex);
246
247 if (e && e->kindIndex == K_NAMESPACE)
248 {
249 struct sRakeSubparser *rake = (struct sRakeSubparser *)s;
250 intArrayRemoveLast (rake->namespaces);
251 }
252 }
253
254 static struct sRakeSubparser rakeSubparser = {
255 .ruby = {
256 .subparser = {
257 .direction = SUBPARSER_SUB_RUNS_BASE,
258 .inputStart = inputStart,
259 },
260 .lineNotify = lineNotify,
261 .enterBlockNotify = enterBlockNotify,
262 .leaveBlockNotify = leaveBlockNotify,
263 },
264 .namespaces = NULL,
265 };
266
initialize(const langType language CTAGS_ATTR_UNUSED)267 static void initialize (const langType language CTAGS_ATTR_UNUSED)
268 {
269 rakeSubparser.namespaces = intArrayNew ();
270 }
271
finalize(langType language CTAGS_ATTR_UNUSED,bool initialized)272 static void finalize (langType language CTAGS_ATTR_UNUSED, bool initialized)
273 {
274 if (rakeSubparser.namespaces)
275 intArrayDelete (rakeSubparser.namespaces);
276 }
277
RakeParser(void)278 extern parserDefinition* RakeParser (void)
279 {
280 static const char *const extensions [] = { "rake", NULL };
281 static const char *const patterns [] = { "Rakefile", NULL };
282
283 static parserDependency dependencies [] = {
284 [0] = { DEPTYPE_SUBPARSER, "Ruby", &rakeSubparser },
285 };
286
287 parserDefinition* const def = parserNew ("Rake");
288
289 def->dependencies = dependencies;
290 def->dependencyCount = ARRAY_SIZE(dependencies);
291 def->kindTable = RakeKinds;
292 def->kindCount = ARRAY_SIZE (RakeKinds);
293 def->extensions = extensions;
294 def->patterns = patterns;
295 def->parser = findRakeTags;
296 def->initialize = initialize;
297 def->finalize = finalize;
298 def->useCork = CORK_QUEUE;
299 def->requestAutomaticFQTag = true;
300
301 return def;
302 }
303