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