From 78a734200fec140612e59ba652f5325bb97aebec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Thu, 2 Mar 2023 15:15:18 +0100 Subject: [PATCH] sql: parse PL/SQL selection directives PLSQL selection directives are of the form $IF boolean_static_expression $THEN text [ $ELSIF boolean_static_expression $THEN text ]... [ $ELSE text ] $END In addition, boolean_static_expression conditions like $$MY_CUSTOM_VAR > 3 can appear which confuses the current parser because it thinks $$ is the start of dollar-quoted string and drops the rest of the code. This patch keeps only the $IF branch and skips all the code between $ELSE or $ELSIF and $END. In addition, this patch also drops $ from all identifiers starting with $$ behind $IF and $ELSIF as these may be conditional variables and not dollar-quoted strings. --- .../args.ctags | 3 + .../expected.tags | 7 ++ .../sql-plsql-selection-directive.d/input.sql | 42 +++++++++++ parsers/sql.c | 72 +++++++++++++++++-- 4 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 Units/parser-sql.r/sql-plsql-selection-directive.d/args.ctags create mode 100644 Units/parser-sql.r/sql-plsql-selection-directive.d/expected.tags create mode 100644 Units/parser-sql.r/sql-plsql-selection-directive.d/input.sql diff --git a/Units/parser-sql.r/sql-plsql-selection-directive.d/args.ctags b/Units/parser-sql.r/sql-plsql-selection-directive.d/args.ctags new file mode 100644 index 0000000000..3eebe6429e --- /dev/null +++ b/Units/parser-sql.r/sql-plsql-selection-directive.d/args.ctags @@ -0,0 +1,3 @@ +--sort=no +--extras=+q +--kinds-SQL=+d diff --git a/Units/parser-sql.r/sql-plsql-selection-directive.d/expected.tags b/Units/parser-sql.r/sql-plsql-selection-directive.d/expected.tags new file mode 100644 index 0000000000..87c768fcd3 --- /dev/null +++ b/Units/parser-sql.r/sql-plsql-selection-directive.d/expected.tags @@ -0,0 +1,7 @@ +demo_pkg input.sql /^create or replace package body demo_pkg is$/;" P +demo_pkg.test_func1 input.sql /^function test_func1 return INTEGER$/;" f package:demo_pkg +test_func1 input.sql /^function test_func1 return INTEGER$/;" f package:demo_pkg +demo_pkg.test_func2 input.sql /^function test_func2 return INTEGER$/;" f package:demo_pkg +test_func2 input.sql /^function test_func2 return INTEGER$/;" f package:demo_pkg +demo_pkg.test_func5 input.sql /^function test_func5 return INTEGER$/;" f package:demo_pkg +test_func5 input.sql /^function test_func5 return INTEGER$/;" f package:demo_pkg diff --git a/Units/parser-sql.r/sql-plsql-selection-directive.d/input.sql b/Units/parser-sql.r/sql-plsql-selection-directive.d/input.sql new file mode 100644 index 0000000000..34c2f7305f --- /dev/null +++ b/Units/parser-sql.r/sql-plsql-selection-directive.d/input.sql @@ -0,0 +1,42 @@ +create or replace package body demo_pkg is + +function test_func1 return INTEGER +as +begin + return 1; +end; + +$IF $$MY_VERSION_CODE > 3 $THEN + +function test_func2 return INTEGER +as +begin + return 3; +end; + +$ELSIF $$MY_VERSION_CODE > 5 $THEN + +function test_func3 return INTEGER +as +begin + return 5; +end; + +$ELSE + +function test_func4 return INTEGER +as +begin + return 7; +end; + +$END + +function test_func5 return INTEGER +as +begin + return 9; +end; + +end demo_pkg; + diff --git a/parsers/sql.c b/parsers/sql.c index 7eadb1f22a..cad5cb1fd4 100644 --- a/parsers/sql.c +++ b/parsers/sql.c @@ -87,6 +87,7 @@ enum eKeywordId { KEYWORD_drop, KEYWORD_else, KEYWORD_elseif, + KEYWORD_elsif, KEYWORD_end, KEYWORD_endif, KEYWORD_event, @@ -177,7 +178,12 @@ typedef enum eTokenType { TOKEN_CLOSE_SQUARE, TOKEN_TILDE, TOKEN_FORWARD_SLASH, - TOKEN_EQUAL + TOKEN_EQUAL, + TOKEN_PREPROC_IF, + TOKEN_PREPROC_ELSIF, + TOKEN_PREPROC_ELSE, + TOKEN_PREPROC_THEN, + TOKEN_PREPROC_END, } tokenType; typedef struct sTokenInfoSQL { @@ -294,6 +300,7 @@ static const keywordTable SqlKeywordTable [] = { { "drop", KEYWORD_drop }, { "else", KEYWORD_else }, { "elseif", KEYWORD_elseif }, + { "elsif", KEYWORD_elsif }, { "end", KEYWORD_end }, { "endif", KEYWORD_endif }, { "event", KEYWORD_event }, @@ -431,6 +438,7 @@ static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = { [KEYWORD_drop] = {1 & 0&1&1&1 & 1&1 & 1}, [KEYWORD_else] = {1 & 1&1&1&1 & 1&1 & 1}, [KEYWORD_elseif] = {1 & 0&0&0&0 & 0&0 & 1}, + [KEYWORD_elsif] = {0 & 0&0&0&0 & 0&1 & 0}, [KEYWORD_end] = {0 & 1&1&1&1 & 0&1 & 1}, [KEYWORD_endif] = {0 & 0&0&0&0 & 0&0 & 1}, [KEYWORD_event] = {0 & 0&0&0&0 & 0&0 & 0}, @@ -725,6 +733,10 @@ static bool isCCFlag(const char *str) * The syntax for dollar-quoted string ca collide with PL/SQL inquiry directive ($$name). * https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-language-fundamentals.html#GUID-E918087C-D5A8-4CEE-841B-5333DE6D4C15 * https://github.com/universal-ctags/ctags/issues/3006 + + * In addition, it can also collide with variable checks in PL/SQL selection directives such as: + * $IF $$my_var > 1 $THEN ... $END + * https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-language-fundamentals.html#GUID-78F2074C-C799-4CF9-9290-EB8473D0C8FB */ static tokenType parseDollarQuote (vString *const string, const int delimiter, int *promise) { @@ -752,8 +764,20 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter, i if (c != delimiter) { - /* damn that's not valid, what can we do? */ + /* not a dollar quote */ + keywordId kw = lookupCaseKeyword (tag+1, Lang_sql); ungetcToInputFile (c); + + if (kw == KEYWORD_if) + return TOKEN_PREPROC_IF; + else if (kw == KEYWORD_elsif) + return TOKEN_PREPROC_ELSIF; + else if (kw == KEYWORD_else) + return TOKEN_PREPROC_ELSE; + else if (kw == KEYWORD_then) + return TOKEN_PREPROC_THEN; + else if (kw == KEYWORD_end) + return TOKEN_PREPROC_END; return TOKEN_UNDEFINED; } @@ -828,7 +852,7 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter, i return TOKEN_STRING; } -static void readToken (tokenInfo *const token) +static void readTokenFull (tokenInfo *const token, bool skippingPreproc) { int c; @@ -952,10 +976,39 @@ static void readToken (tokenInfo *const token) } case '$': - token->type = parseDollarQuote (token->string, c, &token->promise); - token->lineNumber = getInputLineNumber (); - token->filePosition = getInputFilePosition (); - break; + { + tokenType t; + if (skippingPreproc) + { + int d = getcFromInputFile (); + if (d != '$') + ungetcToInputFile (d); + } + t = parseDollarQuote (token->string, c, &token->promise); + if (t == TOKEN_PREPROC_IF) + { + /* skip until $THEN and keep the content of this branch */ + readTokenFull (token, true); + while (!isType (token, TOKEN_PREPROC_THEN) && !isType (token, TOKEN_EOF)) + readTokenFull (token, true); + readTokenFull (token, false); + } + else if (!skippingPreproc && (t == TOKEN_PREPROC_ELSIF || t == TOKEN_PREPROC_ELSE)) + { + /* skip until $END and drop $ELSIF and $ELSE branches */ + readTokenFull (token, true); + while (!isType (token, TOKEN_PREPROC_END) && !isType (token, TOKEN_EOF)) + readTokenFull (token, true); + readTokenFull (token, false); + } + else + { + token->type = t; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + } + break; + } default: if (! isIdentChar1 (c)) @@ -981,6 +1034,11 @@ static void readToken (tokenInfo *const token) } } +static void readToken (tokenInfo *const token) +{ + readTokenFull (token, false); +} + /* * reads an identifier, possibly quoted: * identifier