xref: /Universal-ctags/parsers/rake.c (revision 460d235dfdc27bd7dfabbd6b24958a32ca5b3fa8)
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