xref: /Universal-ctags/parsers/basic.c (revision a503ac773e42678dd3a15911b63dc5311a885ac0)
1 /*
2  *   Copyright (c) 2000-2006, Darren Hiebert, Elias Pschernig
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 BlitzBasic
8  *   (BlitzMax), PureBasic and FreeBasic language files. For now, this is kept
9  *   quite simple - but feel free to ask for more things added any time -
10  *   patches are of course most welcome.
11  *
12  *   FreeBasic:
13  *   - https://www.freebasic.net/wiki/DocToc
14  */
15 
16 /*
17  *   INCLUDE FILES
18  */
19 #include "general.h" /* must always come first */
20 
21 #include <string.h>
22 
23 #include "entry.h"
24 #include "keyword.h"
25 #include "parse.h"
26 #include "read.h"
27 #include "routines.h"
28 #include "trace.h"
29 #include "vstring.h"
30 
31 /*
32  *   DATA DEFINITIONS
33  */
34 typedef enum {
35 	K_CONST,
36 	K_FUNCTION,
37 	K_LABEL,
38 	K_TYPE,
39 	K_VARIABLE,
40 	K_ENUM,
41 	K_NAMESPACE,
42 } BasicKind;
43 
44 typedef enum {
45 	BASIC_FUNCTION_DECL,
46 } basciFunctionRole;
47 
48 static roleDefinition BasicFunctionRoles [] = {
49 	{ true, "decl", "declared" },
50 };
51 
52 static kindDefinition BasicKinds[] = {
53 	{true, 'c', "constant", "constants"},
54 	{true, 'f', "function", "functions",
55 	 .referenceOnly = false, ATTACH_ROLES(BasicFunctionRoles)},
56 	{true, 'l', "label", "labels"},
57 	{true, 't', "type", "types"},
58 	{true, 'v', "variable", "variables"},
59 	{true, 'g', "enum", "enumerations"},
60 	{true, 'n', "namespace", "namespace"},
61 };
62 
63 /* To force to trigger bugs, we make the orders of
64  * enum eKeywordID and BasicKind different. */
65 enum eKeywordID {
66 	KEYWORD_ENUM,
67 	KEYWORD_CONST,
68 	KEYWORD_FUNCTION,
69 	KEYWORD_LABEL,
70 	KEYWORD_TYPE,
71 	KEYWORD_VARIABLE,
72 	KEYWORD_NAMESPACE,
73 	KEYWORD_END,
74 	KEYWORD_ACCESS,
75 	KEYWORD_DECLARE,
76 };
77 typedef int keywordId; /* to allow KEYWORD_NONE */
78 
79 static const keywordTable BasicKeywordTable[] = {
80 	/* freebasic */
81 	{"const", KEYWORD_CONST},
82 	{"dim", KEYWORD_VARIABLE},
83 	{"common", KEYWORD_VARIABLE},
84 	{"function", KEYWORD_FUNCTION},
85 	{"sub", KEYWORD_FUNCTION},
86 	{"private", KEYWORD_ACCESS},
87 	{"public", KEYWORD_ACCESS},
88 	{"property", KEYWORD_FUNCTION},
89 	{"constructor", KEYWORD_FUNCTION},
90 	{"destructor", KEYWORD_FUNCTION},
91 	{"type", KEYWORD_TYPE},
92 	{"enum", KEYWORD_ENUM},
93 	{"namespace", KEYWORD_NAMESPACE},
94 	{"end", KEYWORD_END},
95 	{"declare", KEYWORD_DECLARE},
96 
97 	/* blitzbasic, purebasic */
98 	{"global", KEYWORD_VARIABLE},
99 
100 	/* purebasic */
101 	{"newlist", KEYWORD_VARIABLE},
102 	{"procedure", KEYWORD_FUNCTION},
103 	{"interface", KEYWORD_TYPE},
104 	{"structure", KEYWORD_TYPE},
105 };
106 
107 struct BasicKeywordAttr {
108 	int kind;
109 } keywordAttrs [] = {
110 	[KEYWORD_ENUM]  = {
111 		.kind = K_ENUM,
112 	},
113 	[KEYWORD_CONST] = {
114 		.kind = K_CONST,
115 	},
116 	[KEYWORD_FUNCTION] = {
117 		.kind = K_FUNCTION,
118 	},
119 	[KEYWORD_LABEL] = {
120 		.kind = K_LABEL,
121 	},
122 	[KEYWORD_TYPE] = {
123 		.kind = K_TYPE,
124 	},
125 	[KEYWORD_VARIABLE] = {
126 		.kind = K_VARIABLE,
127 	},
128 	[KEYWORD_NAMESPACE] = {
129 		.kind = K_NAMESPACE,
130 	},
131 	[KEYWORD_END] = {
132 		.kind = KIND_GHOST_INDEX,
133 	},
134 	[KEYWORD_ACCESS] = {
135 		.kind = KIND_GHOST_INDEX,
136 	},
137 	[KEYWORD_DECLARE] = {
138 		.kind = KIND_GHOST_INDEX,
139 	},
140 };
141 
142 struct matchState {
143 	const char *access;
144 	bool end;
145 	bool declaration;
146 };
147 
148 static int currentScope;
149 /*
150  *   FUNCTION DEFINITIONS
151  */
152 
pushScope(int corkIndex)153 static void pushScope (int corkIndex)
154 {
155 #ifdef DEBUG
156 	tagEntryInfo *e = getEntryInCorkQueue (corkIndex);
157 	TRACE_PRINT ("scope push: %s<%d>", e? e->name: "-", corkIndex);
158 #endif
159 	currentScope = corkIndex;
160 }
161 
popScope(void)162 static void popScope (void)
163 {
164 	tagEntryInfo *e = getEntryInCorkQueue (currentScope);
165 #ifdef DEBUG
166 	TRACE_PRINT ("scope pop: %s<%d>", e? e->name: "-", currentScope);
167 #endif
168 	if (e)
169 	{
170 		e->extensionFields.endLine = getInputLineNumber ();
171 		currentScope = e->extensionFields.scopeIndex;
172 	}
173 	else
174 		currentScope = CORK_NIL;
175 }
176 
updateScope(int corkIndex,int kindIndex,int keywordId)177 static void updateScope (int corkIndex, int kindIndex, int keywordId)
178 {
179 	if (corkIndex != CORK_NIL && kindIndex == K_NAMESPACE)
180 		pushScope (corkIndex);
181 	else if (corkIndex == CORK_NIL && kindIndex == K_NAMESPACE)
182 		popScope ();
183 }
184 
keywordToKind(keywordId keywordId)185 static int keywordToKind (keywordId keywordId)
186 {
187 	if (keywordId == KEYWORD_NONE)
188 		return KIND_GHOST_INDEX;
189 	return keywordAttrs [keywordId].kind;
190 }
191 
skipToMatching(char begin,char end,const char * pos)192 static const char *skipToMatching (char begin, char end, const char *pos)
193 {
194 	int counter = 1;
195 	pos++;
196 	while (*pos && counter > 0)
197 	{
198 		if (*pos == end)
199 			counter--;
200 		else if (*pos == begin)
201 			counter++;
202 		else if (*pos == '"')
203 			pos = skipToMatching ('"', '"', pos) - 1;
204 		pos++;
205 	}
206 	return pos;
207 }
208 
nextPos(const char * pos)209 static const char *nextPos (const char *pos)
210 {
211 	if (*pos == '\0')
212 		return pos;
213 
214 	pos++;
215 	switch (*pos)
216 	{
217 		case '(':
218 			pos = skipToMatching ('(', ')', pos);
219 			break;
220 		case '"':
221 			pos = skipToMatching ('"', '"', pos);
222 			break;
223 	}
224 	return pos;
225 }
226 
isIdentChar(char c)227 static bool isIdentChar (char c)
228 {
229 	return c && !isspace (c) && c != '(' && c != ',' && c != '=';
230 }
231 
makeBasicRefTag(vString * name,int kindIndex,int roleIndex)232 static int makeBasicRefTag (vString *name, int kindIndex, int roleIndex)
233 {
234 	int r = makeSimpleRefTag (name, kindIndex, roleIndex);
235 	tagEntryInfo *e = getEntryInCorkQueue (r);
236 	if (e)
237 		e->extensionFields.scopeIndex = currentScope;
238 	return r;
239 }
240 
makeBasicTag(vString * name,int kindIndex)241 static int makeBasicTag (vString *name, int kindIndex)
242 {
243 	return makeBasicRefTag (name, kindIndex, ROLE_DEFINITION_INDEX);
244 }
245 
246 /* Match the name of a dim or const starting at pos. */
extract_dim(char const * pos,BasicKind kind)247 static void extract_dim (char const *pos, BasicKind kind)
248 {
249 	vString *name = vStringNew ();
250 
251 	if (strncasecmp (pos, "shared", 6) == 0)
252 		pos += 6; /* skip keyword "shared" */
253 
254 	while (isspace (*pos))
255 		pos++;
256 
257 	/* capture "dim as String str" */
258 	if (strncasecmp (pos, "as", 2) == 0)
259 	{
260 			pos += 2; /* skip keyword "as" */
261 
262 		while (isspace (*pos))
263 			pos++;
264 		while (!isspace (*pos) && *pos) /* skip next part which is a type */
265 			pos++;
266 		while (isspace (*pos))
267 			pos++;
268 		/* now we are at the name */
269 	}
270 	/* capture "dim as foo ptr bar" */
271 	if (strncasecmp (pos, "ptr", 3) == 0 && isspace(*(pos+3)))
272 	{
273 		pos += 3; /* skip keyword "ptr" */
274 		while (isspace (*pos))
275 			pos++;
276 	}
277 	/*	capture "dim as string * 4096 chunk" */
278 	if (strncmp (pos, "*", 1) == 0)
279 	{
280 		pos += 1; /* skip "*" */
281 		while (isspace (*pos) || isdigit(*pos) || ispunct(*pos))
282 			pos++;
283 	}
284 
285 	for (; isIdentChar (*pos); pos++)
286 		vStringPut (name, *pos);
287 	makeBasicTag (name, kind);
288 
289 	/* if the line contains a ',', we have multiple declarations */
290 	while (*pos && strchr (pos, ','))
291 	{
292 		/* skip all we don't need(e.g. "..., new_array(5), " we skip "(5)") */
293 		while (*pos != ',' && *pos != '\'' && *pos)
294 			pos = nextPos (pos);
295 
296 		if (*pos == '\'')
297 			break; /* break if we are in a comment */
298 
299 		while (isspace (*pos) || *pos == ',')
300 			pos++;
301 
302 		if (*pos == '\'')
303 			break; /* break if we are in a comment */
304 
305 		vStringClear (name);
306 		for (; isIdentChar (*pos); pos++)
307 			vStringPut (name, *pos);
308 		makeBasicTag (name, kind);
309 	}
310 
311 	vStringDelete (name);
312 }
313 
314 /* Match the name of a tag (function, variable, type, ...) starting at pos. */
extract_name(char const * pos,BasicKind kind,struct matchState * state)315 static int extract_name (char const *pos, BasicKind kind, struct matchState *state)
316 {
317 	int r = CORK_NIL;
318 	vString *name = vStringNew ();
319 	for (; isIdentChar (*pos); pos++)
320 		vStringPut (name, *pos);
321 	if (state && state->declaration)
322 	{
323 		if (kind == K_FUNCTION)
324 			r = makeBasicRefTag (name, kind, BASIC_FUNCTION_DECL);
325 	}
326 	else
327 		r = makeBasicTag (name, kind);
328 	vStringDelete (name);
329 
330 	tagEntryInfo *e = getEntryInCorkQueue (r);
331 	if (e && state && state->access)
332 		e->extensionFields.access = eStrdup (state->access);
333 
334 	return r;
335 }
336 
337 /* Match a keyword starting at p (case insensitive). */
match_state_reset(struct matchState * state)338 static void match_state_reset (struct matchState *state)
339 {
340 	state->access = NULL;
341 	state->end = false;
342 	state->declaration =false;
343 }
344 
match_keyword(const char ** cp,vString * buf,struct matchState * state)345 static bool match_keyword (const char **cp, vString *buf,
346 						   struct matchState *state)
347 {
348 	const char *p;
349 
350 	for (p = *cp; *p != '\0' && !isspace((unsigned char)*p); p++)
351 	{
352 		int c = tolower ((unsigned char)*p);
353 		vStringPut (buf, c);
354 	}
355 
356 	int kw = lookupKeyword (vStringValue (buf), getInputLanguage ());
357 	if (kw == KEYWORD_NONE)
358 		return false;
359 
360 	const char *old_p = p;
361 	while (isspace (*p))
362 		p++;
363 
364 	if (kw == KEYWORD_ACCESS)
365 	{
366 		state->access = vStringValue(buf)[1] == 'r'? "private": "public";
367 		*cp = p;
368 		return true;
369 	}
370 	else if (kw == KEYWORD_END)
371 	{
372 		state->end = true;
373 		*cp = p;
374 		return true;
375 	}
376 	else if (kw == KEYWORD_DECLARE)
377 	{
378 		state->declaration = true;
379 		*cp = p;
380 		return true;
381 	}
382 
383 	int kind = keywordToKind (kw);
384 	int index = CORK_NIL;
385 	if (!state->end)
386 	{
387 		/* create tags only if there is some space between the keyword and the identifier */
388 		if (kind != KIND_GHOST_INDEX && old_p == p)
389 			return false;
390 
391 		if (kind == K_VARIABLE)
392 			extract_dim (p, kind); /* extract_dim adds the found tag(s) */
393 		else
394 			index = extract_name (p, kind, state);
395 	}
396 
397 	updateScope (index, kind, kw);
398 
399 	return false;
400 }
401 
402 /* Match a "label:" style label. */
match_colon_label(char const * p)403 static void match_colon_label (char const *p)
404 {
405 	char const *end = p + strlen (p) - 1;
406 	while (isspace (*end))
407 		end--;
408 	if (*end == ':')
409 	{
410 		vString *name = vStringNewNInit (p, end - p);
411 		makeBasicTag (name, K_LABEL);
412 		vStringDelete (name);
413 	}
414 }
415 
416 /* Match a ".label" style label. */
match_dot_label(char const * p)417 static void match_dot_label (char const *p)
418 {
419 	extract_name (p + 1, K_LABEL, NULL);
420 }
421 
findBasicTags(void)422 static void findBasicTags (void)
423 {
424 	const char *line;
425 
426 	currentScope = CORK_NIL;
427 	vString *buf = vStringNew ();
428 
429 	while ((line = (const char *) readLineFromInputFile ()) != NULL)
430 	{
431 		const char *p = line;
432 
433 		while (isspace (*p))
434 			p++;
435 
436 		/* Empty line? */
437 		if (!*p)
438 			continue;
439 
440 		/* REM comment? */
441 		if (strncasecmp (p, "REM", 3) == 0  &&
442 			(isspace (*(p + 3)) || *(p + 3) == '\0'))
443 			continue;
444 
445 		/* Single-quote comment? */
446 		if (*p == '\'')
447 			continue;
448 
449 		/* In Basic, keywords always are at the start of the line. */
450 		struct matchState state;
451 		match_state_reset (&state);
452 		do
453 			vStringClear (buf);
454 		while (match_keyword (&p, buf, &state));
455 
456 
457 		/* Is it a label? */
458 		if (*p == '.')
459 			match_dot_label (p);
460 		else
461 			match_colon_label (p);
462 	}
463 	vStringDelete (buf);
464 }
465 
BasicParser(void)466 parserDefinition *BasicParser (void)
467 {
468 	static char const *extensions[] = { "bas", "bi", "bm", "bb", "pb", NULL };
469 	parserDefinition *def = parserNew ("Basic");
470 	def->kindTable = BasicKinds;
471 	def->kindCount = ARRAY_SIZE (BasicKinds);
472 	def->extensions = extensions;
473 	def->parser = findBasicTags;
474 	def->keywordTable = BasicKeywordTable;
475 	def->keywordCount = ARRAY_SIZE (BasicKeywordTable);
476 	def->useCork = CORK_QUEUE;
477 	return def;
478 }
479