1//-*- coding: utf-8 -*-
2// Scintilla source code edit control
3/** @file LexSQL.cxx
4 ** Lexer for SQL, including PL/SQL and SQL*Plus.
5 ** Improved by Jérôme LAFORGE <jerome.laforge_AT_gmail_DOT_com> from 2010 to 2012.
6 **/
7// Copyright 1998-2012 by Neil Hodgson <neilh@scintilla.org>
8// The License.txt file describes the conditions under which this software may be distributed.
9
10#include <stdlib.h>
11#include <string.h>
12#include <stdio.h>
13#include <stdarg.h>
14#include <assert.h>
15#include <ctype.h>
16
17#include <string>
18#include <string_view>
19#include <vector>
20#include <map>
21#include <algorithm>
22#include <functional>
23
24#include "ILexer.h"
25#include "Scintilla.h"
26#include "SciLexer.h"
27
28#include "WordList.h"
29#include "LexAccessor.h"
30#include "Accessor.h"
31#include "StyleContext.h"
32#include "CharacterSet.h"
33#include "LexerModule.h"
34#include "OptionSet.h"
35#include "SparseState.h"
36#include "DefaultLexer.h"
37
38using namespace Scintilla;
39using namespace Lexilla;
40
41static inline bool IsAWordChar(int ch, bool sqlAllowDottedWord) {
42 if (!sqlAllowDottedWord)
43 return (ch < 0x80) && (isalnum(ch) || ch == '_');
44 else
45 return (ch < 0x80) && (isalnum(ch) || ch == '_' || ch == '.');
46}
47
48static inline bool IsAWordStart(int ch) {
49 return (ch < 0x80) && (isalpha(ch) || ch == '_');
50}
51
52static inline bool IsADoxygenChar(int ch) {
53 return (islower(ch) || ch == '$' || ch == '@' ||
54 ch == '\\' || ch == '&' || ch == '<' ||
55 ch == '>' || ch == '#' || ch == '{' ||
56 ch == '}' || ch == '[' || ch == ']');
57}
58
59static inline bool IsANumberChar(int ch, int chPrev) {
60 // Not exactly following number definition (several dots are seen as OK, etc.)
61 // but probably enough in most cases.
62 return (ch < 0x80) &&
63 (isdigit(ch) || toupper(ch) == 'E' ||
64 ch == '.' || ((ch == '-' || ch == '+') && chPrev < 0x80 && toupper(chPrev) == 'E'));
65}
66
67typedef unsigned int sql_state_t;
68
69class SQLStates {
70public :
71 void Set(Sci_Position lineNumber, unsigned short int sqlStatesLine) {
72 sqlStatement.Set(lineNumber, sqlStatesLine);
73 }
74
75 sql_state_t IgnoreWhen (sql_state_t sqlStatesLine, bool enable) {
76 if (enable)
77 sqlStatesLine |= MASK_IGNORE_WHEN;
78 else
79 sqlStatesLine &= ~MASK_IGNORE_WHEN;
80
81 return sqlStatesLine;
82 }
83
84 sql_state_t IntoCondition (sql_state_t sqlStatesLine, bool enable) {
85 if (enable)
86 sqlStatesLine |= MASK_INTO_CONDITION;
87 else
88 sqlStatesLine &= ~MASK_INTO_CONDITION;
89
90 return sqlStatesLine;
91 }
92
93 sql_state_t IntoExceptionBlock (sql_state_t sqlStatesLine, bool enable) {
94 if (enable)
95 sqlStatesLine |= MASK_INTO_EXCEPTION;
96 else
97 sqlStatesLine &= ~MASK_INTO_EXCEPTION;
98
99 return sqlStatesLine;
100 }
101
102 sql_state_t IntoDeclareBlock (sql_state_t sqlStatesLine, bool enable) {
103 if (enable)
104 sqlStatesLine |= MASK_INTO_DECLARE;
105 else
106 sqlStatesLine &= ~MASK_INTO_DECLARE;
107
108 return sqlStatesLine;
109 }
110
111 sql_state_t IntoMergeStatement (sql_state_t sqlStatesLine, bool enable) {
112 if (enable)
113 sqlStatesLine |= MASK_MERGE_STATEMENT;
114 else
115 sqlStatesLine &= ~MASK_MERGE_STATEMENT;
116
117 return sqlStatesLine;
118 }
119
120 sql_state_t CaseMergeWithoutWhenFound (sql_state_t sqlStatesLine, bool found) {
121 if (found)
122 sqlStatesLine |= MASK_CASE_MERGE_WITHOUT_WHEN_FOUND;
123 else
124 sqlStatesLine &= ~MASK_CASE_MERGE_WITHOUT_WHEN_FOUND;
125
126 return sqlStatesLine;
127 }
128 sql_state_t IntoSelectStatementOrAssignment (sql_state_t sqlStatesLine, bool found) {
129 if (found)
130 sqlStatesLine |= MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT;
131 else
132 sqlStatesLine &= ~MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT;
133 return sqlStatesLine;
134 }
135
136 sql_state_t BeginCaseBlock (sql_state_t sqlStatesLine) {
137 if ((sqlStatesLine & MASK_NESTED_CASES) < MASK_NESTED_CASES) {
138 sqlStatesLine++;
139 }
140 return sqlStatesLine;
141 }
142
143 sql_state_t EndCaseBlock (sql_state_t sqlStatesLine) {
144 if ((sqlStatesLine & MASK_NESTED_CASES) > 0) {
145 sqlStatesLine--;
146 }
147 return sqlStatesLine;
148 }
149
150 sql_state_t IntoCreateStatement (sql_state_t sqlStatesLine, bool enable) {
151 if (enable)
152 sqlStatesLine |= MASK_INTO_CREATE;
153 else
154 sqlStatesLine &= ~MASK_INTO_CREATE;
155
156 return sqlStatesLine;
157 }
158
159 sql_state_t IntoCreateViewStatement (sql_state_t sqlStatesLine, bool enable) {
160 if (enable)
161 sqlStatesLine |= MASK_INTO_CREATE_VIEW;
162 else
163 sqlStatesLine &= ~MASK_INTO_CREATE_VIEW;
164
165 return sqlStatesLine;
166 }
167
168 sql_state_t IntoCreateViewAsStatement (sql_state_t sqlStatesLine, bool enable) {
169 if (enable)
170 sqlStatesLine |= MASK_INTO_CREATE_VIEW_AS_STATEMENT;
171 else
172 sqlStatesLine &= ~MASK_INTO_CREATE_VIEW_AS_STATEMENT;
173
174 return sqlStatesLine;
175 }
176
177 bool IsIgnoreWhen (sql_state_t sqlStatesLine) {
178 return (sqlStatesLine & MASK_IGNORE_WHEN) != 0;
179 }
180
181 bool IsIntoCondition (sql_state_t sqlStatesLine) {
182 return (sqlStatesLine & MASK_INTO_CONDITION) != 0;
183 }
184
185 bool IsIntoCaseBlock (sql_state_t sqlStatesLine) {
186 return (sqlStatesLine & MASK_NESTED_CASES) != 0;
187 }
188
189 bool IsIntoExceptionBlock (sql_state_t sqlStatesLine) {
190 return (sqlStatesLine & MASK_INTO_EXCEPTION) != 0;
191 }
192 bool IsIntoSelectStatementOrAssignment (sql_state_t sqlStatesLine) {
193 return (sqlStatesLine & MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT) != 0;
194 }
195 bool IsCaseMergeWithoutWhenFound (sql_state_t sqlStatesLine) {
196 return (sqlStatesLine & MASK_CASE_MERGE_WITHOUT_WHEN_FOUND) != 0;
197 }
198
199 bool IsIntoDeclareBlock (sql_state_t sqlStatesLine) {
200 return (sqlStatesLine & MASK_INTO_DECLARE) != 0;
201 }
202
203 bool IsIntoMergeStatement (sql_state_t sqlStatesLine) {
204 return (sqlStatesLine & MASK_MERGE_STATEMENT) != 0;
205 }
206
207 bool IsIntoCreateStatement (sql_state_t sqlStatesLine) {
208 return (sqlStatesLine & MASK_INTO_CREATE) != 0;
209 }
210
211 bool IsIntoCreateViewStatement (sql_state_t sqlStatesLine) {
212 return (sqlStatesLine & MASK_INTO_CREATE_VIEW) != 0;
213 }
214
215 bool IsIntoCreateViewAsStatement (sql_state_t sqlStatesLine) {
216 return (sqlStatesLine & MASK_INTO_CREATE_VIEW_AS_STATEMENT) != 0;
217 }
218
219 sql_state_t ForLine(Sci_Position lineNumber) {
220 return sqlStatement.ValueAt(lineNumber);
221 }
222
223 SQLStates() {}
224
225private :
226 SparseState <sql_state_t> sqlStatement;
227 enum {
228 MASK_NESTED_CASES = 0x0001FF,
229 MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT = 0x000200,
230 MASK_CASE_MERGE_WITHOUT_WHEN_FOUND = 0x000400,
231 MASK_MERGE_STATEMENT = 0x000800,
232 MASK_INTO_DECLARE = 0x001000,
233 MASK_INTO_EXCEPTION = 0x002000,
234 MASK_INTO_CONDITION = 0x004000,
235 MASK_IGNORE_WHEN = 0x008000,
236 MASK_INTO_CREATE = 0x010000,
237 MASK_INTO_CREATE_VIEW = 0x020000,
238 MASK_INTO_CREATE_VIEW_AS_STATEMENT = 0x040000
239 };
240};
241
242// Options used for LexerSQL
243struct OptionsSQL {
244 bool fold;
245 bool foldAtElse;
246 bool foldComment;
247 bool foldCompact;
248 bool foldOnlyBegin;
249 bool sqlBackticksIdentifier;
250 bool sqlNumbersignComment;
251 bool sqlBackslashEscapes;
252 bool sqlAllowDottedWord;
253 OptionsSQL() {
254 fold = false;
255 foldAtElse = false;
256 foldComment = false;
257 foldCompact = false;
258 foldOnlyBegin = false;
259 sqlBackticksIdentifier = false;
260 sqlNumbersignComment = false;
261 sqlBackslashEscapes = false;
262 sqlAllowDottedWord = false;
263 }
264};
265
266static const char * const sqlWordListDesc[] = {
267 "Keywords",
268 "Database Objects",
269 "PLDoc",
270 "SQL*Plus",
271 "User Keywords 1",
272 "User Keywords 2",
273 "User Keywords 3",
274 "User Keywords 4",
275 0
276};
277
278struct OptionSetSQL : public OptionSet<OptionsSQL> {
279 OptionSetSQL() {
280 DefineProperty("fold", &OptionsSQL::fold);
281
282 DefineProperty("fold.sql.at.else", &OptionsSQL::foldAtElse,
283 "This option enables SQL folding on a \"ELSE\" and \"ELSIF\" line of an IF statement.");
284
285 DefineProperty("fold.comment", &OptionsSQL::foldComment);
286
287 DefineProperty("fold.compact", &OptionsSQL::foldCompact);
288
289 DefineProperty("fold.sql.only.begin", &OptionsSQL::foldOnlyBegin);
290
291 DefineProperty("lexer.sql.backticks.identifier", &OptionsSQL::sqlBackticksIdentifier);
292
293 DefineProperty("lexer.sql.numbersign.comment", &OptionsSQL::sqlNumbersignComment,
294 "If \"lexer.sql.numbersign.comment\" property is set to 0 a line beginning with '#' will not be a comment.");
295
296 DefineProperty("sql.backslash.escapes", &OptionsSQL::sqlBackslashEscapes,
297 "Enables backslash as an escape character in SQL.");
298
299 DefineProperty("lexer.sql.allow.dotted.word", &OptionsSQL::sqlAllowDottedWord,
300 "Set to 1 to colourise recognized words with dots "
301 "(recommended for Oracle PL/SQL objects).");
302
303 DefineWordListSets(sqlWordListDesc);
304 }
305};
306
307class LexerSQL : public DefaultLexer {
308public :
309 LexerSQL() : DefaultLexer("sql", SCLEX_SQL) {}
310
311 virtual ~LexerSQL() {}
312
313 int SCI_METHOD Version () const override {
314 return lvRelease5;
315 }
316
317 void SCI_METHOD Release() override {
318 delete this;
319 }
320
321 const char * SCI_METHOD PropertyNames() override {
322 return osSQL.PropertyNames();
323 }
324
325 int SCI_METHOD PropertyType(const char *name) override {
326 return osSQL.PropertyType(name);
327 }
328
329 const char * SCI_METHOD DescribeProperty(const char *name) override {
330 return osSQL.DescribeProperty(name);
331 }
332
333 Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override {
334 if (osSQL.PropertySet(&options, key, val)) {
335 return 0;
336 }
337 return -1;
338 }
339
340 const char * SCI_METHOD PropertyGet(const char *key) override {
341 return osSQL.PropertyGet(key);
342 }
343
344 const char * SCI_METHOD DescribeWordListSets() override {
345 return osSQL.DescribeWordListSets();
346 }
347
348 Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;
349 void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
350 void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
351
352 void * SCI_METHOD PrivateCall(int, void *) override {
353 return 0;
354 }
355
356 static ILexer5 *LexerFactorySQL() {
357 return new LexerSQL();
358 }
359private:
360 bool IsStreamCommentStyle(int style) {
361 return style == SCE_SQL_COMMENT ||
362 style == SCE_SQL_COMMENTDOC ||
363 style == SCE_SQL_COMMENTDOCKEYWORD ||
364 style == SCE_SQL_COMMENTDOCKEYWORDERROR;
365 }
366
367 bool IsCommentStyle (int style) {
368 switch (style) {
369 case SCE_SQL_COMMENT :
370 case SCE_SQL_COMMENTDOC :
371 case SCE_SQL_COMMENTLINE :
372 case SCE_SQL_COMMENTLINEDOC :
373 case SCE_SQL_COMMENTDOCKEYWORD :
374 case SCE_SQL_COMMENTDOCKEYWORDERROR :
375 return true;
376 default :
377 return false;
378 }
379 }
380
381 bool IsCommentLine (Sci_Position line, LexAccessor &styler) {
382 Sci_Position pos = styler.LineStart(line);
383 Sci_Position eol_pos = styler.LineStart(line + 1) - 1;
384 for (Sci_Position i = pos; i + 1 < eol_pos; i++) {
385 int style = styler.StyleAt(i);
386 // MySQL needs -- comments to be followed by space or control char
387 if (style == SCE_SQL_COMMENTLINE && styler.Match(i, "--"))
388 return true;
389 else if (!IsASpaceOrTab(styler[i]))
390 return false;
391 }
392 return false;
393 }
394
395 OptionsSQL options;
396 OptionSetSQL osSQL;
397 SQLStates sqlStates;
398
399 WordList keywords1;
400 WordList keywords2;
401 WordList kw_pldoc;
402 WordList kw_sqlplus;
403 WordList kw_user1;
404 WordList kw_user2;
405 WordList kw_user3;
406 WordList kw_user4;
407};
408
409Sci_Position SCI_METHOD LexerSQL::WordListSet(int n, const char *wl) {
410 WordList *wordListN = 0;
411 switch (n) {
412 case 0:
413 wordListN = &keywords1;
414 break;
415 case 1:
416 wordListN = &keywords2;
417 break;
418 case 2:
419 wordListN = &kw_pldoc;
420 break;
421 case 3:
422 wordListN = &kw_sqlplus;
423 break;
424 case 4:
425 wordListN = &kw_user1;
426 break;
427 case 5:
428 wordListN = &kw_user2;
429 break;
430 case 6:
431 wordListN = &kw_user3;
432 break;
433 case 7:
434 wordListN = &kw_user4;
435 }
436 Sci_Position firstModification = -1;
437 if (wordListN) {
438 WordList wlNew;
439 wlNew.Set(wl);
440 if (*wordListN != wlNew) {
441 wordListN->Set(wl);
442 firstModification = 0;
443 }
444 }
445 return firstModification;
446}
447
448void SCI_METHOD LexerSQL::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
449 LexAccessor styler(pAccess);
450 StyleContext sc(startPos, length, initStyle, styler);
451 int styleBeforeDCKeyword = SCE_SQL_DEFAULT;
452
453 for (; sc.More(); sc.Forward()) {
454 // Determine if the current state should terminate.
455 switch (sc.state) {
456 case SCE_SQL_OPERATOR:
457 sc.SetState(SCE_SQL_DEFAULT);
458 break;
459 case SCE_SQL_NUMBER:
460 // We stop the number definition on non-numerical non-dot non-eE non-sign char
461 if (!IsANumberChar(sc.ch, sc.chPrev)) {
462 sc.SetState(SCE_SQL_DEFAULT);
463 }
464 break;
465 case SCE_SQL_IDENTIFIER:
466 if (!IsAWordChar(sc.ch, options.sqlAllowDottedWord)) {
467 int nextState = SCE_SQL_DEFAULT;
468 char s[1000];
469 sc.GetCurrentLowered(s, sizeof(s));
470 if (keywords1.InList(s)) {
471 sc.ChangeState(SCE_SQL_WORD);
472 } else if (keywords2.InList(s)) {
473 sc.ChangeState(SCE_SQL_WORD2);
474 } else if (kw_sqlplus.InListAbbreviated(s, '~')) {
475 sc.ChangeState(SCE_SQL_SQLPLUS);
476 if (strncmp(s, "rem", 3) == 0) {
477 nextState = SCE_SQL_SQLPLUS_COMMENT;
478 } else if (strncmp(s, "pro", 3) == 0) {
479 nextState = SCE_SQL_SQLPLUS_PROMPT;
480 }
481 } else if (kw_user1.InList(s)) {
482 sc.ChangeState(SCE_SQL_USER1);
483 } else if (kw_user2.InList(s)) {
484 sc.ChangeState(SCE_SQL_USER2);
485 } else if (kw_user3.InList(s)) {
486 sc.ChangeState(SCE_SQL_USER3);
487 } else if (kw_user4.InList(s)) {
488 sc.ChangeState(SCE_SQL_USER4);
489 }
490 sc.SetState(nextState);
491 }
492 break;
493 case SCE_SQL_QUOTEDIDENTIFIER:
494 if (sc.ch == 0x60) {
495 if (sc.chNext == 0x60) {
496 sc.Forward(); // Ignore it
497 } else {
498 sc.ForwardSetState(SCE_SQL_DEFAULT);
499 }
500 }
501 break;
502 case SCE_SQL_COMMENT:
503 if (sc.Match('*', '/')) {
504 sc.Forward();
505 sc.ForwardSetState(SCE_SQL_DEFAULT);
506 }
507 break;
508 case SCE_SQL_COMMENTDOC:
509 if (sc.Match('*', '/')) {
510 sc.Forward();
511 sc.ForwardSetState(SCE_SQL_DEFAULT);
512 } else if (sc.ch == '@' || sc.ch == '\\') { // Doxygen support
513 // Verify that we have the conditions to mark a comment-doc-keyword
514 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
515 styleBeforeDCKeyword = SCE_SQL_COMMENTDOC;
516 sc.SetState(SCE_SQL_COMMENTDOCKEYWORD);
517 }
518 }
519 break;
520 case SCE_SQL_COMMENTLINE:
521 case SCE_SQL_COMMENTLINEDOC:
522 case SCE_SQL_SQLPLUS_COMMENT:
523 case SCE_SQL_SQLPLUS_PROMPT:
524 if (sc.atLineStart) {
525 sc.SetState(SCE_SQL_DEFAULT);
526 }
527 break;
528 case SCE_SQL_COMMENTDOCKEYWORD:
529 if ((styleBeforeDCKeyword == SCE_SQL_COMMENTDOC) && sc.Match('*', '/')) {
530 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
531 sc.Forward();
532 sc.ForwardSetState(SCE_SQL_DEFAULT);
533 } else if (!IsADoxygenChar(sc.ch)) {
534 char s[100];
535 sc.GetCurrentLowered(s, sizeof(s));
536 if (!isspace(sc.ch) || !kw_pldoc.InList(s + 1)) {
537 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
538 }
539 sc.SetState(styleBeforeDCKeyword);
540 }
541 break;
542 case SCE_SQL_CHARACTER:
543 if (options.sqlBackslashEscapes && sc.ch == '\\') {
544 sc.Forward();
545 } else if (sc.ch == '\'') {
546 if (sc.chNext == '\'') {
547 sc.Forward();
548 } else {
549 sc.ForwardSetState(SCE_SQL_DEFAULT);
550 }
551 }
552 break;
553 case SCE_SQL_STRING:
554 if (options.sqlBackslashEscapes && sc.ch == '\\') {
555 // Escape sequence
556 sc.Forward();
557 } else if (sc.ch == '\"') {
558 if (sc.chNext == '\"') {
559 sc.Forward();
560 } else {
561 sc.ForwardSetState(SCE_SQL_DEFAULT);
562 }
563 }
564 break;
565 case SCE_SQL_QOPERATOR:
566 // Locate the unique Q operator character
567 sc.Complete();
568 char qOperator = 0x00;
569 for (Sci_Position styleStartPos = sc.currentPos; styleStartPos > 0; --styleStartPos) {
570 if (styler.StyleAt(styleStartPos - 1) != SCE_SQL_QOPERATOR) {
571 qOperator = styler.SafeGetCharAt(styleStartPos + 2);
572 break;
573 }
574 }
575
576 char qComplement = 0x00;
577
578 if (qOperator == '<') {
579 qComplement = '>';
580 } else if (qOperator == '(') {
581 qComplement = ')';
582 } else if (qOperator == '{') {
583 qComplement = '}';
584 } else if (qOperator == '[') {
585 qComplement = ']';
586 } else {
587 qComplement = qOperator;
588 }
589
590 if (sc.Match(qComplement, '\'')) {
591 sc.Forward();
592 sc.ForwardSetState(SCE_SQL_DEFAULT);
593 }
594 break;
595 }
596
597 // Determine if a new state should be entered.
598 if (sc.state == SCE_SQL_DEFAULT) {
599 if (sc.Match('q', '\'') || sc.Match('Q', '\'')) {
600 sc.SetState(SCE_SQL_QOPERATOR);
601 sc.Forward();
602 } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext)) ||
603 ((sc.ch == '-' || sc.ch == '+') && IsADigit(sc.chNext) && !IsADigit(sc.chPrev))) {
604 sc.SetState(SCE_SQL_NUMBER);
605 } else if (IsAWordStart(sc.ch)) {
606 sc.SetState(SCE_SQL_IDENTIFIER);
607 } else if (sc.ch == 0x60 && options.sqlBackticksIdentifier) {
608 sc.SetState(SCE_SQL_QUOTEDIDENTIFIER);
609 } else if (sc.Match('/', '*')) {
610 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Doxygen doc. style
611 sc.SetState(SCE_SQL_COMMENTDOC);
612 } else {
613 sc.SetState(SCE_SQL_COMMENT);
614 }
615 sc.Forward(); // Eat the * so it isn't used for the end of the comment
616 } else if (sc.Match('-', '-')) {
617 // MySQL requires a space or control char after --
618 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
619 // Perhaps we should enforce that with proper property:
620 //~ } else if (sc.Match("-- ")) {
621 sc.SetState(SCE_SQL_COMMENTLINE);
622 } else if (sc.ch == '#' && options.sqlNumbersignComment) {
623 sc.SetState(SCE_SQL_COMMENTLINEDOC);
624 } else if (sc.ch == '\'') {
625 sc.SetState(SCE_SQL_CHARACTER);
626 } else if (sc.ch == '\"') {
627 sc.SetState(SCE_SQL_STRING);
628 } else if (isoperator(static_cast<char>(sc.ch))) {
629 sc.SetState(SCE_SQL_OPERATOR);
630 }
631 }
632 }
633 sc.Complete();
634}
635
636void SCI_METHOD LexerSQL::Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
637 if (!options.fold)
638 return;
639 LexAccessor styler(pAccess);
640 Sci_PositionU endPos = startPos + length;
641 int visibleChars = 0;
642 Sci_Position lineCurrent = styler.GetLine(startPos);
643 int levelCurrent = SC_FOLDLEVELBASE;
644
645 if (lineCurrent > 0) {
646 // Backtrack to previous line in case need to fix its fold status for folding block of single-line comments (i.e. '--').
647 Sci_Position lastNLPos = -1;
648 // And keep going back until we find an operator ';' followed
649 // by white-space and/or comments. This will improve folding.
650 while (--startPos > 0) {
651 char ch = styler[startPos];
652 if (ch == '\n' || (ch == '\r' && styler[startPos + 1] != '\n')) {
653 lastNLPos = startPos;
654 } else if (ch == ';' &&
655 styler.StyleAt(startPos) == SCE_SQL_OPERATOR) {
656 bool isAllClear = true;
657 for (Sci_Position tempPos = startPos + 1;
658 tempPos < lastNLPos;
659 ++tempPos) {
660 int tempStyle = styler.StyleAt(tempPos);
661 if (!IsCommentStyle(tempStyle)
662 && tempStyle != SCE_SQL_DEFAULT) {
663 isAllClear = false;
664 break;
665 }
666 }
667 if (isAllClear) {
668 startPos = lastNLPos + 1;
669 break;
670 }
671 }
672 }
673 lineCurrent = styler.GetLine(startPos);
674 if (lineCurrent > 0)
675 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
676 }
677 // And because folding ends at ';', keep going until we find one
678 // Otherwise if create ... view ... as is split over multiple
679 // lines the folding won't always update immediately.
680 Sci_PositionU docLength = styler.Length();
681 for (; endPos < docLength; ++endPos) {
682 if (styler.SafeGetCharAt(endPos) == ';') {
683 break;
684 }
685 }
686
687 int levelNext = levelCurrent;
688 char chNext = styler[startPos];
689 int styleNext = styler.StyleAt(startPos);
690 int style = initStyle;
691 bool endFound = false;
692 bool isUnfoldingIgnored = false;
693 // this statementFound flag avoids to fold when the statement is on only one line by ignoring ELSE or ELSIF
694 // eg. "IF condition1 THEN ... ELSIF condition2 THEN ... ELSE ... END IF;"
695 bool statementFound = false;
696 sql_state_t sqlStatesCurrentLine = 0;
697 if (!options.foldOnlyBegin) {
698 sqlStatesCurrentLine = sqlStates.ForLine(lineCurrent);
699 }
700 for (Sci_PositionU i = startPos; i < endPos; i++) {
701 char ch = chNext;
702 chNext = styler.SafeGetCharAt(i + 1);
703 int stylePrev = style;
704 style = styleNext;
705 styleNext = styler.StyleAt(i + 1);
706 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
707 if (atEOL || (!IsCommentStyle(style) && ch == ';')) {
708 if (endFound) {
709 //Maybe this is the end of "EXCEPTION" BLOCK (eg. "BEGIN ... EXCEPTION ... END;")
710 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, false);
711 }
712 // set endFound and isUnfoldingIgnored to false if EOL is reached or ';' is found
713 endFound = false;
714 isUnfoldingIgnored = false;
715 }
716 if ((!IsCommentStyle(style) && ch == ';')) {
717 if (sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)) {
718 // This is the end of "MERGE" statement.
719 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
720 levelNext--;
721 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, false);
722 levelNext--;
723 }
724 if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine))
725 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, false);
726 if (sqlStates.IsIntoCreateStatement(sqlStatesCurrentLine)) {
727 if (sqlStates.IsIntoCreateViewStatement(sqlStatesCurrentLine)) {
728 if (sqlStates.IsIntoCreateViewAsStatement(sqlStatesCurrentLine)) {
729 levelNext--;
730 sqlStatesCurrentLine = sqlStates.IntoCreateViewAsStatement(sqlStatesCurrentLine, false);
731 }
732 sqlStatesCurrentLine = sqlStates.IntoCreateViewStatement(sqlStatesCurrentLine, false);
733 }
734 sqlStatesCurrentLine = sqlStates.IntoCreateStatement(sqlStatesCurrentLine, false);
735 }
736 }
737 if (ch == ':' && chNext == '=' && !IsCommentStyle(style))
738 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);
739
740 if (options.foldComment && IsStreamCommentStyle(style)) {
741 if (!IsStreamCommentStyle(stylePrev)) {
742 levelNext++;
743 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
744 // Comments don't end at end of line and the next character may be unstyled.
745 levelNext--;
746 }
747 }
748 if (options.foldComment && (style == SCE_SQL_COMMENTLINE)) {
749 // MySQL needs -- comments to be followed by space or control char
750 if ((ch == '-') && (chNext == '-')) {
751 char chNext2 = styler.SafeGetCharAt(i + 2);
752 char chNext3 = styler.SafeGetCharAt(i + 3);
753 if (chNext2 == '{' || chNext3 == '{') {
754 levelNext++;
755 } else if (chNext2 == '}' || chNext3 == '}') {
756 levelNext--;
757 }
758 }
759 }
760 // Fold block of single-line comments (i.e. '--').
761 if (options.foldComment && atEOL && IsCommentLine(lineCurrent, styler)) {
762 if (!IsCommentLine(lineCurrent - 1, styler) && IsCommentLine(lineCurrent + 1, styler))
763 levelNext++;
764 else if (IsCommentLine(lineCurrent - 1, styler) && !IsCommentLine(lineCurrent + 1, styler))
765 levelNext--;
766 }
767 if (style == SCE_SQL_OPERATOR) {
768 if (ch == '(') {
769 if (levelCurrent > levelNext)
770 levelCurrent--;
771 levelNext++;
772 } else if (ch == ')') {
773 levelNext--;
774 } else if ((!options.foldOnlyBegin) && ch == ';') {
775 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, false);
776 }
777 }
778 // If new keyword (cannot trigger on elseif or nullif, does less tests)
779 if (style == SCE_SQL_WORD && stylePrev != SCE_SQL_WORD) {
780 const int MAX_KW_LEN = 9; // Maximum length of folding keywords
781 char s[MAX_KW_LEN + 2];
782 unsigned int j = 0;
783 for (; j < MAX_KW_LEN + 1; j++) {
784 if (!iswordchar(styler[i + j])) {
785 break;
786 }
787 s[j] = static_cast<char>(tolower(styler[i + j]));
788 }
789 if (j == MAX_KW_LEN + 1) {
790 // Keyword too long, don't test it
791 s[0] = '\0';
792 } else {
793 s[j] = '\0';
794 }
795 if (!options.foldOnlyBegin &&
796 strcmp(s, "select") == 0) {
797 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);
798 } else if (strcmp(s, "if") == 0) {
799 if (endFound) {
800 endFound = false;
801 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
802 // this end isn't for begin block, but for if block ("end if;")
803 // so ignore previous "end" by increment levelNext.
804 levelNext++;
805 }
806 } else {
807 if (!options.foldOnlyBegin)
808 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
809 if (levelCurrent > levelNext) {
810 // doesn't include this line into the folding block
811 // because doesn't hide IF (eg "END; IF")
812 levelCurrent = levelNext;
813 }
814 }
815 } else if (!options.foldOnlyBegin &&
816 strcmp(s, "then") == 0 &&
817 sqlStates.IsIntoCondition(sqlStatesCurrentLine)) {
818 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, false);
819 if (!options.foldOnlyBegin) {
820 if (levelCurrent > levelNext) {
821 levelCurrent = levelNext;
822 }
823 if (!statementFound)
824 levelNext++;
825
826 statementFound = true;
827 } else if (levelCurrent > levelNext) {
828 // doesn't include this line into the folding block
829 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
830 levelCurrent = levelNext;
831 }
832 } else if (strcmp(s, "loop") == 0 ||
833 strcmp(s, "case") == 0) {
834 if (endFound) {
835 endFound = false;
836 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
837 // this end isn't for begin block, but for loop block ("end loop;") or case block ("end case;")
838 // so ignore previous "end" by increment levelNext.
839 levelNext++;
840 }
841 if ((!options.foldOnlyBegin) && strcmp(s, "case") == 0) {
842 sqlStatesCurrentLine = sqlStates.EndCaseBlock(sqlStatesCurrentLine);
843 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
844 levelNext--; //again for the "end case;" and block when
845 }
846 } else if (!options.foldOnlyBegin) {
847 if (strcmp(s, "case") == 0) {
848 sqlStatesCurrentLine = sqlStates.BeginCaseBlock(sqlStatesCurrentLine);
849 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
850 }
851
852 if (levelCurrent > levelNext)
853 levelCurrent = levelNext;
854
855 if (!statementFound)
856 levelNext++;
857
858 statementFound = true;
859 } else if (levelCurrent > levelNext) {
860 // doesn't include this line into the folding block
861 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
862 levelCurrent = levelNext;
863 }
864 } else if ((!options.foldOnlyBegin) && (
865 // folding for ELSE and ELSIF block only if foldAtElse is set
866 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
867 options.foldAtElse && !statementFound) && strcmp(s, "elsif") == 0) {
868 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
869 levelCurrent--;
870 levelNext--;
871 } else if ((!options.foldOnlyBegin) && (
872 // folding for ELSE and ELSIF block only if foldAtElse is set
873 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
874 options.foldAtElse && !statementFound) && strcmp(s, "else") == 0) {
875 // prevent also ELSE is on the same line (eg. "ELSE ... END IF;")
876 statementFound = true;
877 if (sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) && sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
878 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
879 levelNext++;
880 } else {
881 // we are in same case "} ELSE {" in C language
882 levelCurrent--;
883 }
884 } else if (strcmp(s, "begin") == 0) {
885 levelNext++;
886 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, false);
887 } else if ((strcmp(s, "end") == 0) ||
888 // SQL Anywhere permits IF ... ELSE ... ENDIF
889 // will only be active if "endif" appears in the
890 // keyword list.
891 (strcmp(s, "endif") == 0)) {
892 endFound = true;
893 levelNext--;
894 if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine) && !sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
895 levelNext--;
896 if (levelNext < SC_FOLDLEVELBASE) {
897 levelNext = SC_FOLDLEVELBASE;
898 isUnfoldingIgnored = true;
899 }
900 } else if ((!options.foldOnlyBegin) &&
901 strcmp(s, "when") == 0 &&
902 !sqlStates.IsIgnoreWhen(sqlStatesCurrentLine) &&
903 !sqlStates.IsIntoExceptionBlock(sqlStatesCurrentLine) && (
904 sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) ||
905 sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)
906 )
907 ) {
908 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
909
910 // Don't foldind when CASE and WHEN are on the same line (with flag statementFound) (eg. "CASE selector WHEN expression1 THEN sequence_of_statements1;\n")
911 // and same way for MERGE statement.
912 if (!statementFound) {
913 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
914 levelCurrent--;
915 levelNext--;
916 }
917 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
918 }
919 } else if ((!options.foldOnlyBegin) && strcmp(s, "exit") == 0) {
920 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, true);
921 } else if ((!options.foldOnlyBegin) && !sqlStates.IsIntoDeclareBlock(sqlStatesCurrentLine) && strcmp(s, "exception") == 0) {
922 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, true);
923 } else if ((!options.foldOnlyBegin) &&
924 (strcmp(s, "declare") == 0 ||
925 strcmp(s, "function") == 0 ||
926 strcmp(s, "procedure") == 0 ||
927 strcmp(s, "package") == 0)) {
928 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, true);
929 } else if ((!options.foldOnlyBegin) &&
930 strcmp(s, "merge") == 0) {
931 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, true);
932 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
933 levelNext++;
934 statementFound = true;
935 } else if ((!options.foldOnlyBegin) &&
936 strcmp(s, "create") == 0) {
937 sqlStatesCurrentLine = sqlStates.IntoCreateStatement(sqlStatesCurrentLine, true);
938 } else if ((!options.foldOnlyBegin) &&
939 strcmp(s, "view") == 0 &&
940 sqlStates.IsIntoCreateStatement(sqlStatesCurrentLine)) {
941 sqlStatesCurrentLine = sqlStates.IntoCreateViewStatement(sqlStatesCurrentLine, true);
942 } else if ((!options.foldOnlyBegin) &&
943 strcmp(s, "as") == 0 &&
944 sqlStates.IsIntoCreateViewStatement(sqlStatesCurrentLine) &&
945 ! sqlStates.IsIntoCreateViewAsStatement(sqlStatesCurrentLine)) {
946 sqlStatesCurrentLine = sqlStates.IntoCreateViewAsStatement(sqlStatesCurrentLine, true);
947 levelNext++;
948 }
949 }
950 if (atEOL) {
951 int levelUse = levelCurrent;
952 int lev = levelUse | levelNext << 16;
953 if (visibleChars == 0 && options.foldCompact)
954 lev |= SC_FOLDLEVELWHITEFLAG;
955 if (levelUse < levelNext)
956 lev |= SC_FOLDLEVELHEADERFLAG;
957 if (lev != styler.LevelAt(lineCurrent)) {
958 styler.SetLevel(lineCurrent, lev);
959 }
960 lineCurrent++;
961 levelCurrent = levelNext;
962 visibleChars = 0;
963 statementFound = false;
964 if (!options.foldOnlyBegin)
965 sqlStates.Set(lineCurrent, sqlStatesCurrentLine);
966 }
967 if (!isspacechar(ch)) {
968 visibleChars++;
969 }
970 }
971}
972
973LexerModule lmSQL(SCLEX_SQL, LexerSQL::LexerFactorySQL, "sql", sqlWordListDesc);
974