xref: /Universal-ctags/parsers/nsis.c (revision 6b1a862e526d5017f9f212a321f59d67c859d521)
1 /*
2 *   Copyright (c) 2000-2002, Darren Hiebert
3 *   Copyright (c) 2009-2011, Enrico Tröger
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 for generating tags for NSIS scripts
9 *   (https://en.wikipedia.org/wiki/Nullsoft_Scriptable_Install_System).
10 *
11 *   Based on sh.c.
12 */
13 
14 /*
15 *   INCLUDE FILES
16 */
17 #include "general.h"	/* must always come first */
18 
19 #include <string.h>
20 
21 #include "entry.h"
22 #include "parse.h"
23 #include "read.h"
24 #include "vstring.h"
25 #include "routines.h"
26 
27 /*
28 *   DATA DEFINITIONS
29 */
30 typedef enum {
31 	K_SECTION,
32 	K_FUNCTION,
33 	K_VARIABLE,
34 	K_DEFINITION,
35 	K_MACRO,
36 	K_SECTION_GROUP,
37 	K_MACRO_PARAM,
38 	K_LANGSTR,
39 	K_SCRIPT,
40 } NsisKind;
41 
42 typedef enum {
43 	NSIS_SCRIPT_INCLUDED,
44 } nsisScriptRole;
45 
46 static roleDefinition NsisScriptRoles [] = {
47 	{ true, "included",  "included with !include" },
48 };
49 
50 static kindDefinition NsisKinds [] = {
51 	{ true, 's', "section", "sections"},
52 	{ true, 'f', "function", "functions"},
53 	{ true, 'v', "variable", "variables"},
54 	{ true, 'd', "definition", "definitions"},
55 	{ true, 'm', "macro", "macros"},
56 	{ true, 'S', "sectionGroup", "section groups"},
57 	{ false, 'p', "macroparam", "macro parameters"},
58 	{ true, 'l', "langstr", "language strings"},
59 	{ true, 'i', "script", "NSIS scripts",
60 	  .referenceOnly = true, ATTACH_ROLES(NsisScriptRoles)},
61 };
62 
63 typedef enum {
64 	F_LANGID,
65 } nsisField;
66 
67 static fieldDefinition NsisFields[] = {
68 	{ .name = "langid",
69 	  .description = "language identifier specified in (License)LangString commands",
70 	  .enabled = true },
71 };
72 
73 /*
74 *   FUNCTION DEFINITIONS
75 */
76 
skipWhitespace(const unsigned char * cp)77 static const unsigned char* skipWhitespace (const unsigned char* cp)
78 {
79 	while (isspace ((int) *cp))
80 		++cp;
81 	return cp;
82 }
83 
skipFlags(const unsigned char * cp)84 static const unsigned char* skipFlags (const unsigned char* cp)
85 {
86 	while (*cp == '/')
87 	{
88 		++cp;
89 		while (! isspace ((int) *cp))
90 			++cp;
91 		while (isspace ((int) *cp))
92 			++cp;
93 	}
94 	return cp;
95 }
96 
makeSimpleTagWithScope(vString * name,int kindIndex,int parentCorkIndex)97 static int makeSimpleTagWithScope(vString *name, int kindIndex, int parentCorkIndex)
98 {
99 	tagEntryInfo e;
100 	initTagEntry (&e, vStringValue (name), kindIndex);
101 	e.extensionFields.scopeIndex = parentCorkIndex;
102 	return makeTagEntry (&e);
103 }
104 
105 #define lineStartingWith(CP,EXPECTED,EOL)								\
106 	(strncasecmp ((const char*) CP, EXPECTED, strlen(EXPECTED)) == 0	\
107 		&& (EOL ? (isspace ((int) CP [strlen(EXPECTED)]) || CP [strlen(EXPECTED)] == '\0') \
108 			: isspace ((int) CP [strlen(EXPECTED)])))
109 
110 #define fillName(NAME,CP,CONDITION)				\
111 	while (CONDITION)							\
112 	{											\
113 		vStringPut ((NAME), (int) *(CP));		\
114 		++(CP);									\
115 	}											\
116 	do {} while (0)
117 
parseSection(const unsigned char * cp,vString * name,int kindIndex,int scopeIndex,int * corkIndex)118 static const unsigned char* parseSection (const unsigned char* cp, vString *name,
119 										  int kindIndex, int scopeIndex, int *corkIndex)
120 {
121 	cp = skipWhitespace (cp);
122 	cp = skipFlags (cp);
123 	cp = skipWhitespace (cp);
124 
125 	if (corkIndex)
126 		*corkIndex = CORK_NIL;
127 
128 	if (strpbrk((const char *)cp, "'`\""))
129 	{
130 		const unsigned char terminator = *cp;
131 
132 		cp++;
133 		if (*cp == terminator)
134 		{
135 			/* An empty section.
136 			 * See https://nsis.sourceforge.io/Docs/Chapter4.html#sectionsettext
137 			 */
138 			anonGenerate (name,
139 						  (kindIndex == K_SECTION
140 						   ? "AnonymousSection"
141 						   : "AnonymousSectionGroup"),
142 						  kindIndex);
143 			cp++;
144 		}
145 		else if (*cp == '\0')
146 			return cp;
147 		else
148 		{
149 			int in_escape = 0;
150 			do
151 			{
152 				vStringPut (name, (int) *cp);
153 				++cp;
154 
155 				if (*cp == '\0')
156 					break;
157 
158 				/*
159 				 * Ignore `"' in `$\"' as the terminator of quotation.
160 				 */
161 				if (*cp == '$' && in_escape == 0)
162 					in_escape++;
163 				else if (*cp == '\\' && in_escape == 1)
164 					in_escape++;
165 				else if (*cp == terminator && in_escape == 2)
166 					/*
167 					 * This `"' is not a terminator of quotation;
168 					 * set in_escape to 3.
169 					 */
170 					in_escape++;
171 				else
172 					in_escape = 0;
173 
174 				if ((in_escape != 3) && *cp == terminator)
175 				{
176 					++cp;
177 					break;
178 				}
179 			}
180 			while (1);
181 		}
182 	}
183 	else
184 	{
185 		while (isalnum ((int) *cp)
186 			   || *cp == '_' || *cp == '-' || *cp == '.' || *cp == '!'
187 			   || *cp == '$' || *cp == '{' || *cp == '}' || *cp == '(' || *cp == ')')
188 		{
189 			vStringPut (name, (int) *cp);
190 			++cp;
191 		}
192 	}
193 	int r = makeSimpleTagWithScope (name, kindIndex, scopeIndex);
194 	if (corkIndex)
195 		*corkIndex = r;
196 	if (vStringLength (name) > 0)
197 	{
198 		/*
199 		 * Try to capture section_index_output.
200 		 */
201 		vStringClear (name);
202 		cp = skipWhitespace (cp);
203 
204 		fillName (name, cp, (isalnum ((int) *cp) || *cp == '_'));
205 
206 		if (vStringLength (name) > 0)
207 		{
208 			makeSimpleTag (name, K_DEFINITION);
209 			vStringClear (name);
210 		}
211 	}
212 	return cp;
213 }
214 
parseLangString(const unsigned char * cp,vString * name)215 static const unsigned char* parseLangString (const unsigned char* cp, vString *name)
216 {
217 	cp = skipWhitespace (cp);
218 
219 	/* `^' is not explained in the nsis reference manual. However, it is used
220 	 * in gvim.
221 	 * e.g.
222 	 * https://github.com/vim/vim/blob/3dabd718f4b2d8e09de9e2ec73832620b91c2f79/nsis/lang/english.nsi
223 	 */
224 	fillName (name, cp, (isalnum ((int) *cp) || *cp == '_' || *cp == '^'));
225 
226 	if (vStringLength (name) > 0)
227 	{
228 		int r = makeSimpleTag (name, K_LANGSTR);
229 		if (r == CORK_NIL)
230 			goto out;
231 		vStringClear (name);
232 
233 		cp = skipWhitespace (cp);
234 		fillName (name, cp, ((*cp != '\0') && (!isspace ((int) *cp))));
235 		if (vStringLength (name) > 0)
236 		{
237 			attachParserFieldToCorkEntry (r, NsisFields[F_LANGID].ftype,
238 										  vStringValue (name));
239 			vStringClear (name);
240 		}
241 	}
242  out:
243 	return cp;
244 }
245 
findNsisTags(void)246 static void findNsisTags (void)
247 {
248 	int sectionGroupIndex = CORK_NIL;
249 	vString *name = vStringNew ();
250 	const unsigned char *line;
251 
252 	while ((line = readLineFromInputFile ()) != NULL)
253 	{
254 		const unsigned char* cp = line;
255 
256 		while (isspace (*cp))
257 			cp++;
258 
259 		if (*cp == '#' || *cp == ';')
260 			continue;
261 
262 		/* functions */
263 		if (lineStartingWith (cp, "function", false))
264 		{
265 			cp += 8;
266 			cp = skipWhitespace (cp);
267 
268 			fillName (name, cp,
269 					  (isalnum ((int) *cp) || *cp == '_' || *cp == '-' || *cp == '.' || *cp == '!'));
270 
271 			makeSimpleTag (name, K_FUNCTION);
272 			vStringClear (name);
273 		}
274 		/* variables */
275 		else if (lineStartingWith (cp, "var", false))
276 		{
277 			cp += 3;
278 			cp = skipWhitespace (cp);
279 			cp = skipFlags (cp);
280 
281 			fillName (name, cp, (isalnum ((int) *cp) || *cp == '_'));
282 
283 			makeSimpleTag (name, K_VARIABLE);
284 			vStringClear (name);
285 		}
286 		/* section groups */
287 		else if (lineStartingWith (cp, "sectiongroup", false))
288 		{
289 			cp += 12;
290 			cp = parseSection (cp, name, K_SECTION_GROUP, CORK_NIL, &sectionGroupIndex);
291 		}
292 		else if (lineStartingWith (cp, "sectiongroupend", true))
293 		{
294 			cp += 15;
295 			sectionGroupIndex = CORK_NIL;
296 		}
297 		/* sections */
298 		else if (lineStartingWith (cp, "section", false))
299 		{
300 			cp += 7;
301 			cp = parseSection (cp, name, K_SECTION, sectionGroupIndex, NULL);
302 		}
303 		/* LangString */
304 		else if (lineStartingWith (cp, "langstring", false))
305 		{
306 			cp += 10;
307 			cp = parseLangString (cp, name);
308 		}
309 		/* LicenseLangString */
310 		else if (lineStartingWith (cp, "licenselangstring", false))
311 		{
312 			cp += 17;
313 			cp = parseLangString (cp, name);
314 		}
315 		/* definitions */
316 		else if (lineStartingWith (cp, "!define", false))
317 		{
318 			cp += 7;
319 			cp = skipWhitespace (cp);
320 			cp = skipFlags (cp);
321 
322 			fillName (name, cp, (isalnum ((int) *cp) || *cp == '_'));
323 
324 			makeSimpleTag (name, K_DEFINITION);
325 			vStringClear (name);
326 		}
327 		/* macro */
328 		else if (lineStartingWith (cp, "!macro", false))
329 		{
330 			cp += 6;
331 			cp = skipWhitespace (cp);
332 			cp = skipFlags (cp);
333 
334 			fillName (name, cp, (isalnum ((int) *cp) || *cp == '_'));
335 
336 			int index = makeSimpleTag (name, K_MACRO);
337 			if (vStringLength (name) > 0)
338 			{
339 				while (1)
340 				{
341 					vStringClear (name);
342 					cp = skipWhitespace (cp);
343 					fillName (name, cp, (isalnum ((int) *cp) || *cp == '_'));
344 					if (vStringLength (name) == 0)
345 						break;
346 					makeSimpleTagWithScope (name, K_MACRO_PARAM, index);
347 				}
348 			}
349 		}
350 		/* include */
351 		else if (lineStartingWith (cp, "!include", false))
352 		{
353 			cp += 8;
354 
355 			/* !include [/NONFATAL] [/CHARSET=ACP|OEM|CP#|UTF8|UTF16LE|UTF16BE] file */
356 			cp = skipWhitespace (cp);
357 
358 			/* /NONFATAL */
359 			cp = skipFlags (cp);
360 			cp = skipWhitespace (cp);
361 
362 			/* /CHARSET */
363 			cp = skipFlags (cp);
364 			cp = skipWhitespace (cp);
365 
366 			fillName (name, cp, (*cp != '\0' && *cp != ';' && *cp != '#'));
367 			vStringStripTrailing (name);
368 
369 			if (vStringLength (name) > 0)
370 			{
371 				makeSimpleRefTag (name, K_SCRIPT, NSIS_SCRIPT_INCLUDED);
372 				vStringClear (name);
373 			}
374 			/* TODO: capture !addincludedir */
375 		}
376 	}
377 	vStringDelete (name);
378 }
379 
NsisParser(void)380 extern parserDefinition* NsisParser (void)
381 {
382 	static const char *const extensions [] = {
383 		"nsi", "nsh", NULL
384 	};
385 	parserDefinition* def = parserNew ("NSIS");
386 	def->kindTable  = NsisKinds;
387 	def->kindCount  = ARRAY_SIZE (NsisKinds);
388 	def->extensions = extensions;
389 	def->fieldTable = NsisFields;
390 	def->fieldCount = ARRAY_SIZE (NsisFields);
391 	def->parser     = findNsisTags;
392 	def->useCork    = CORK_QUEUE;
393 	return def;
394 }
395