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