diff --git a/Units/parser-basic.r/freebasic-access.d/args.ctags b/Units/parser-basic.r/freebasic-access.d/args.ctags new file mode 100644 index 0000000000..4c0ce9b90f --- /dev/null +++ b/Units/parser-basic.r/freebasic-access.d/args.ctags @@ -0,0 +1,2 @@ +--sort=no +--fields=+{access} diff --git a/Units/parser-basic.r/freebasic-access.d/expected.tags b/Units/parser-basic.r/freebasic-access.d/expected.tags new file mode 100644 index 0000000000..bb891b5232 --- /dev/null +++ b/Units/parser-basic.r/freebasic-access.d/expected.tags @@ -0,0 +1,2 @@ +i_am_private input.bas /^Private Sub i_am_private$/;" f access:private +i_am_public input.bas /^Public Sub i_am_public$/;" f access:public diff --git a/Units/parser-basic.r/freebasic-access.d/input.bas b/Units/parser-basic.r/freebasic-access.d/input.bas new file mode 100644 index 0000000000..46da136a37 --- /dev/null +++ b/Units/parser-basic.r/freebasic-access.d/input.bas @@ -0,0 +1,6 @@ +# Taken from https://documentation.help/FreeBASIC/KeyPgPublic.html +Private Sub i_am_private +End Sub + +Public Sub i_am_public +End Sub diff --git a/Units/parser-basic.r/freebasic-namespace.d/args.ctags b/Units/parser-basic.r/freebasic-namespace.d/args.ctags new file mode 100644 index 0000000000..a60a455e15 --- /dev/null +++ b/Units/parser-basic.r/freebasic-namespace.d/args.ctags @@ -0,0 +1,2 @@ +--sort=no +--fields=+e diff --git a/Units/parser-basic.r/freebasic-namespace.d/expected.tags b/Units/parser-basic.r/freebasic-namespace.d/expected.tags new file mode 100644 index 0000000000..859edd8813 --- /dev/null +++ b/Units/parser-basic.r/freebasic-namespace.d/expected.tags @@ -0,0 +1,6 @@ +first input.bas /^namespace first 'first defines something'$/;" n end:12 +first_func input.bas /^ sub first_func$/;" f namespace:first +second input.bas /^ namespace second 'second defines something'$/;" n namespace:first end:11 +first_func input.bas /^ sub first_func 'oh a second first_func$/;" f namespace:first.second +second_func input.bas /^ sub second_func$/;" f namespace:first.second +first_func input.bas /^sub first_func 'oh another first_func$/;" f diff --git a/Units/parser-basic.r/freebasic-namespace.d/input.bas b/Units/parser-basic.r/freebasic-namespace.d/input.bas new file mode 100644 index 0000000000..820ee560ec --- /dev/null +++ b/Units/parser-basic.r/freebasic-namespace.d/input.bas @@ -0,0 +1,17 @@ +namespace first 'first defines something' + sub first_func + end sub + + namespace second 'second defines something' + sub first_func 'oh a second first_func + end sub + + sub second_func + end sub + end namespace 'ignored' +end namespace + +sub first_func 'oh another first_func +end sub + +' vim: bs=2 sw=2 ss=2 ts=2 nu et ft=basic diff --git a/parsers/basic.c b/parsers/basic.c index b1286e64d2..2eda84e2ae 100644 --- a/parsers/basic.c +++ b/parsers/basic.c @@ -8,6 +8,9 @@ * (BlitzMax), PureBasic and FreeBasic language files. For now, this is kept * quite simple - but feel free to ask for more things added any time - * patches are of course most welcome. + * + * FreeBasic: + * - https://www.freebasic.net/wiki/DocToc */ /* @@ -17,9 +20,12 @@ #include +#include "entry.h" +#include "keyword.h" #include "parse.h" #include "read.h" #include "routines.h" +#include "trace.h" #include "vstring.h" /* @@ -31,56 +37,143 @@ typedef enum { K_LABEL, K_TYPE, K_VARIABLE, - K_ENUM + K_ENUM, + K_NAMESPACE, } BasicKind; -typedef struct { - char const *token; - BasicKind kind; -} KeyWord; - static kindDefinition BasicKinds[] = { {true, 'c', "constant", "constants"}, {true, 'f', "function", "functions"}, {true, 'l', "label", "labels"}, {true, 't', "type", "types"}, {true, 'v', "variable", "variables"}, - {true, 'g', "enum", "enumerations"} + {true, 'g', "enum", "enumerations"}, + {true, 'n', "namespace", "namespace"}, +}; + +/* To force to trigger bugs, we make the orders of + * enum eKeywordID and BasicKind different. */ +enum eKeywordID { + KEYWORD_ENUM, + KEYWORD_CONST, + KEYWORD_FUNCTION, + KEYWORD_LABEL, + KEYWORD_TYPE, + KEYWORD_VARIABLE, + KEYWORD_NAMESPACE, + KEYWORD_END, + KEYWORD_ACCESS, }; +typedef int keywordId; /* to allow KEYWORD_NONE */ -static KeyWord basic_keywords[] = { +static const keywordTable BasicKeywordTable[] = { /* freebasic */ - {"const", K_CONST}, - {"dim", K_VARIABLE}, - {"common", K_VARIABLE}, - {"function", K_FUNCTION}, - {"sub", K_FUNCTION}, - {"private sub", K_FUNCTION}, - {"public sub", K_FUNCTION}, - {"private function", K_FUNCTION}, - {"public function", K_FUNCTION}, - {"property", K_FUNCTION}, - {"constructor", K_FUNCTION}, - {"destructor", K_FUNCTION}, - {"type", K_TYPE}, - {"enum", K_ENUM}, + {"const", KEYWORD_CONST}, + {"dim", KEYWORD_VARIABLE}, + {"common", KEYWORD_VARIABLE}, + {"function", KEYWORD_FUNCTION}, + {"sub", KEYWORD_FUNCTION}, + {"private", KEYWORD_ACCESS}, + {"public", KEYWORD_ACCESS}, + {"property", KEYWORD_FUNCTION}, + {"constructor", KEYWORD_FUNCTION}, + {"destructor", KEYWORD_FUNCTION}, + {"type", KEYWORD_TYPE}, + {"enum", KEYWORD_ENUM}, + {"namespace", KEYWORD_NAMESPACE}, + {"end", KEYWORD_END}, /* blitzbasic, purebasic */ - {"global", K_VARIABLE}, + {"global", KEYWORD_VARIABLE}, /* purebasic */ - {"newlist", K_VARIABLE}, - {"procedure", K_FUNCTION}, - {"interface", K_TYPE}, - {"structure", K_TYPE}, + {"newlist", KEYWORD_VARIABLE}, + {"procedure", KEYWORD_FUNCTION}, + {"interface", KEYWORD_TYPE}, + {"structure", KEYWORD_TYPE}, +}; + +struct BasicKeywordAttr { + int kind; +} keywordAttrs [] = { + [KEYWORD_ENUM] = { + .kind = K_ENUM, + }, + [KEYWORD_CONST] = { + .kind = K_CONST, + }, + [KEYWORD_FUNCTION] = { + .kind = K_FUNCTION, + }, + [KEYWORD_LABEL] = { + .kind = K_LABEL, + }, + [KEYWORD_TYPE] = { + .kind = K_TYPE, + }, + [KEYWORD_VARIABLE] = { + .kind = K_VARIABLE, + }, + [KEYWORD_NAMESPACE] = { + .kind = K_NAMESPACE, + }, + [KEYWORD_END] = { + .kind = KIND_GHOST_INDEX, + }, + [KEYWORD_ACCESS] = { + .kind = KIND_GHOST_INDEX, + }, +}; - {NULL, 0} +struct matchState { + const char *access; + bool end; }; +static int currentScope; /* * FUNCTION DEFINITIONS */ +static void pushScope (int corkIndex) +{ +#ifdef DEBUG + tagEntryInfo *e = getEntryInCorkQueue (corkIndex); + TRACE_PRINT ("scope push: %s<%d>", e? e->name: "-", corkIndex); +#endif + currentScope = corkIndex; +} + +static void popScope (void) +{ + tagEntryInfo *e = getEntryInCorkQueue (currentScope); +#ifdef DEBUG + TRACE_PRINT ("scope pop: %s<%d>", e? e->name: "-", currentScope); +#endif + if (e) + { + e->extensionFields.endLine = getInputLineNumber (); + currentScope = e->extensionFields.scopeIndex; + } + else + currentScope = CORK_NIL; +} + +static void updateScope (int corkIndex, int kindIndex, int keywordId) +{ + if (corkIndex != CORK_NIL && kindIndex == K_NAMESPACE) + pushScope (corkIndex); + else if (corkIndex == CORK_NIL && kindIndex == K_NAMESPACE) + popScope (); +} + +static int keywordToKind (keywordId keywordId) +{ + if (keywordId == KEYWORD_NONE) + return KIND_GHOST_INDEX; + return keywordAttrs [keywordId].kind; +} + static const char *skipToMatching (char begin, char end, const char *pos) { int counter = 1; @@ -121,6 +214,15 @@ static bool isIdentChar (char c) return c && !isspace (c) && c != '(' && c != ',' && c != '='; } +static int makeBasicTag (vString *name, int kindIndex) +{ + int r = makeSimpleTag (name, kindIndex); + tagEntryInfo *e = getEntryInCorkQueue (r); + if (e) + e->extensionFields.scopeIndex = currentScope; + return r; +} + /* Match the name of a dim or const starting at pos. */ static void extract_dim (char const *pos, BasicKind kind) { @@ -162,7 +264,7 @@ static void extract_dim (char const *pos, BasicKind kind) for (; isIdentChar (*pos); pos++) vStringPut (name, *pos); - makeSimpleTag (name, kind); + makeBasicTag (name, kind); /* if the line contains a ',', we have multiple declarations */ while (*pos && strchr (pos, ',')) @@ -183,47 +285,85 @@ static void extract_dim (char const *pos, BasicKind kind) vStringClear (name); for (; isIdentChar (*pos); pos++) vStringPut (name, *pos); - makeSimpleTag (name, kind); + makeBasicTag (name, kind); } vStringDelete (name); } /* Match the name of a tag (function, variable, type, ...) starting at pos. */ -static void extract_name (char const *pos, BasicKind kind) +static int extract_name (char const *pos, BasicKind kind, struct matchState *state) { + int r = CORK_NIL; vString *name = vStringNew (); for (; isIdentChar (*pos); pos++) vStringPut (name, *pos); - makeSimpleTag (name, kind); + r = makeBasicTag (name, kind); vStringDelete (name); + + tagEntryInfo *e = getEntryInCorkQueue (r); + if (e && state && state->access) + e->extensionFields.access = eStrdup (state->access); + + return r; } /* Match a keyword starting at p (case insensitive). */ -static bool match_keyword (const char *p, KeyWord const *kw) +static void match_state_reset (struct matchState *state) +{ + state->access = NULL; + state->end = false; +} + +static bool match_keyword (const char **cp, vString *buf, + struct matchState *state) { - size_t i; - const char *old_p; - for (i = 0; i < strlen (kw->token); i++) + const char *p; + + for (p = *cp; *p != '\0' && !isspace((unsigned char)*p); p++) { - if (tolower (p[i]) != kw->token[i]) - return false; + int c = tolower ((unsigned char)*p); + vStringPut (buf, c); } - p += i; - old_p = p; + int kw = lookupKeyword (vStringValue (buf), getInputLanguage ()); + if (kw == KEYWORD_NONE) + return false; + + const char *old_p = p; while (isspace (*p)) p++; - /* create tags only if there is some space between the keyword and the identifier */ - if (old_p == p) - return false; + if (kw == KEYWORD_ACCESS) + { + state->access = vStringValue(buf)[1] == 'r'? "private": "public"; + *cp = p; + return true; + } + else if (kw == KEYWORD_END) + { + state->end = true; + *cp = p; + return true; + } - if (kw->kind == K_VARIABLE) - extract_dim (p, kw->kind); /* extract_dim adds the found tag(s) */ - else - extract_name (p, kw->kind); - return true; + int kind = keywordToKind (kw); + int index = CORK_NIL; + if (!state->end) + { + /* create tags only if there is some space between the keyword and the identifier */ + if (kind != KIND_GHOST_INDEX && old_p == p) + return false; + + if (kind == K_VARIABLE) + extract_dim (p, kind); /* extract_dim adds the found tag(s) */ + else + index = extract_name (p, kind, state); + } + + updateScope (index, kind, kw); + + return false; } /* Match a "label:" style label. */ @@ -234,9 +374,8 @@ static void match_colon_label (char const *p) end--; if (*end == ':') { - vString *name = vStringNew (); - vStringNCatS (name, p, end - p); - makeSimpleTag (name, K_LABEL); + vString *name = vStringNewNInit (p, end - p); + makeBasicTag (name, K_LABEL); vStringDelete (name); } } @@ -244,18 +383,19 @@ static void match_colon_label (char const *p) /* Match a ".label" style label. */ static void match_dot_label (char const *p) { - if (*p == '.') - extract_name (p + 1, K_LABEL); + extract_name (p + 1, K_LABEL, NULL); } static void findBasicTags (void) { const char *line; + currentScope = CORK_NIL; + vString *buf = vStringNew (); + while ((line = (const char *) readLineFromInputFile ()) != NULL) { const char *p = line; - KeyWord const *kw; while (isspace (*p)) p++; @@ -274,8 +414,12 @@ static void findBasicTags (void) continue; /* In Basic, keywords always are at the start of the line. */ - for (kw = basic_keywords; kw->token; kw++) - if (match_keyword (p, kw)) break; + struct matchState state; + match_state_reset (&state); + do + vStringClear (buf); + while (match_keyword (&p, buf, &state)); + /* Is it a label? */ if (*p == '.') @@ -283,6 +427,7 @@ static void findBasicTags (void) else match_colon_label (p); } + vStringDelete (buf); } parserDefinition *BasicParser (void) @@ -293,5 +438,8 @@ parserDefinition *BasicParser (void) def->kindCount = ARRAY_SIZE (BasicKinds); def->extensions = extensions; def->parser = findBasicTags; + def->keywordTable = BasicKeywordTable; + def->keywordCount = ARRAY_SIZE (BasicKeywordTable); + def->useCork = CORK_QUEUE; return def; }