Upgraded to scintilla 3.2.3
[TortoiseGit.git] / ext / scintilla / lexers / LexSQL.cxx
blob6d97789adb4493931350bddf6cc3da259995f5d5
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) {
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 == '+');
66 class SQLStates {
67 public :
68 void Set(int lineNumber, unsigned short int sqlStatesLine) {
69 sqlStatement.Set(lineNumber, sqlStatesLine);
72 unsigned short int IgnoreWhen (unsigned short int sqlStatesLine, bool enable) {
73 if (enable)
74 sqlStatesLine |= MASK_IGNORE_WHEN;
75 else
76 sqlStatesLine &= ~MASK_IGNORE_WHEN;
78 return sqlStatesLine;
81 unsigned short int IntoCondition (unsigned short int sqlStatesLine, bool enable) {
82 if (enable)
83 sqlStatesLine |= MASK_INTO_CONDITION;
84 else
85 sqlStatesLine &= ~MASK_INTO_CONDITION;
87 return sqlStatesLine;
90 unsigned short int IntoExceptionBlock (unsigned short int sqlStatesLine, bool enable) {
91 if (enable)
92 sqlStatesLine |= MASK_INTO_EXCEPTION;
93 else
94 sqlStatesLine &= ~MASK_INTO_EXCEPTION;
96 return sqlStatesLine;
99 unsigned short int IntoDeclareBlock (unsigned short int sqlStatesLine, bool enable) {
100 if (enable)
101 sqlStatesLine |= MASK_INTO_DECLARE;
102 else
103 sqlStatesLine &= ~MASK_INTO_DECLARE;
105 return sqlStatesLine;
108 unsigned short int IntoMergeStatement (unsigned short int sqlStatesLine, bool enable) {
109 if (enable)
110 sqlStatesLine |= MASK_MERGE_STATEMENT;
111 else
112 sqlStatesLine &= ~MASK_MERGE_STATEMENT;
114 return sqlStatesLine;
117 unsigned short int CaseMergeWithoutWhenFound (unsigned short int sqlStatesLine, bool found) {
118 if (found)
119 sqlStatesLine |= MASK_CASE_MERGE_WITHOUT_WHEN_FOUND;
120 else
121 sqlStatesLine &= ~MASK_CASE_MERGE_WITHOUT_WHEN_FOUND;
123 return sqlStatesLine;
125 unsigned short int IntoSelectStatementOrAssignment (unsigned short int sqlStatesLine, bool found) {
126 if (found)
127 sqlStatesLine |= MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT;
128 else
129 sqlStatesLine &= ~MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT;
130 return sqlStatesLine;
133 unsigned short int BeginCaseBlock (unsigned short int sqlStatesLine) {
134 if ((sqlStatesLine & MASK_NESTED_CASES) < MASK_NESTED_CASES) {
135 sqlStatesLine++;
137 return sqlStatesLine;
140 unsigned short int EndCaseBlock (unsigned short int sqlStatesLine) {
141 if ((sqlStatesLine & MASK_NESTED_CASES) > 0) {
142 sqlStatesLine--;
144 return sqlStatesLine;
147 bool IsIgnoreWhen (unsigned short int sqlStatesLine) {
148 return (sqlStatesLine & MASK_IGNORE_WHEN) != 0;
151 bool IsIntoCondition (unsigned short int sqlStatesLine) {
152 return (sqlStatesLine & MASK_INTO_CONDITION) != 0;
155 bool IsIntoCaseBlock (unsigned short int sqlStatesLine) {
156 return (sqlStatesLine & MASK_NESTED_CASES) != 0;
159 bool IsIntoExceptionBlock (unsigned short int sqlStatesLine) {
160 return (sqlStatesLine & MASK_INTO_EXCEPTION) != 0;
162 bool IsIntoSelectStatementOrAssignment (unsigned short int sqlStatesLine) {
163 return (sqlStatesLine & MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT) != 0;
165 bool IsCaseMergeWithoutWhenFound (unsigned short int sqlStatesLine) {
166 return (sqlStatesLine & MASK_CASE_MERGE_WITHOUT_WHEN_FOUND) != 0;
169 bool IsIntoDeclareBlock (unsigned short int sqlStatesLine) {
170 return (sqlStatesLine & MASK_INTO_DECLARE) != 0;
173 bool IsIntoMergeStatement (unsigned short int sqlStatesLine) {
174 return (sqlStatesLine & MASK_MERGE_STATEMENT) != 0;
177 unsigned short int ForLine(int lineNumber) {
178 return sqlStatement.ValueAt(lineNumber);
181 SQLStates() {}
183 private :
184 SparseState <unsigned short int> sqlStatement;
185 enum {
186 MASK_NESTED_CASES = 0x01FF,
187 MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT = 0x0200,
188 MASK_CASE_MERGE_WITHOUT_WHEN_FOUND = 0x0400,
189 MASK_MERGE_STATEMENT = 0x0800,
190 MASK_INTO_DECLARE = 0x1000,
191 MASK_INTO_EXCEPTION = 0x2000,
192 MASK_INTO_CONDITION = 0x4000,
193 MASK_IGNORE_WHEN = 0x8000
197 // Options used for LexerSQL
198 struct OptionsSQL {
199 bool fold;
200 bool foldAtElse;
201 bool foldComment;
202 bool foldCompact;
203 bool foldOnlyBegin;
204 bool sqlBackticksIdentifier;
205 bool sqlNumbersignComment;
206 bool sqlBackslashEscapes;
207 bool sqlAllowDottedWord;
208 OptionsSQL() {
209 fold = false;
210 foldAtElse = false;
211 foldComment = false;
212 foldCompact = false;
213 foldOnlyBegin = false;
214 sqlBackticksIdentifier = false;
215 sqlNumbersignComment = false;
216 sqlBackslashEscapes = false;
217 sqlAllowDottedWord = false;
221 static const char * const sqlWordListDesc[] = {
222 "Keywords",
223 "Database Objects",
224 "PLDoc",
225 "SQL*Plus",
226 "User Keywords 1",
227 "User Keywords 2",
228 "User Keywords 3",
229 "User Keywords 4",
233 struct OptionSetSQL : public OptionSet<OptionsSQL> {
234 OptionSetSQL() {
235 DefineProperty("fold", &OptionsSQL::fold);
237 DefineProperty("fold.sql.at.else", &OptionsSQL::foldAtElse,
238 "This option enables SQL folding on a \"ELSE\" and \"ELSIF\" line of an IF statement.");
240 DefineProperty("fold.comment", &OptionsSQL::foldComment);
242 DefineProperty("fold.compact", &OptionsSQL::foldCompact);
244 DefineProperty("fold.sql.only.begin", &OptionsSQL::foldOnlyBegin);
246 DefineProperty("lexer.sql.backticks.identifier", &OptionsSQL::sqlBackticksIdentifier);
248 DefineProperty("lexer.sql.numbersign.comment", &OptionsSQL::sqlNumbersignComment,
249 "If \"lexer.sql.numbersign.comment\" property is set to 0 a line beginning with '#' will not be a comment.");
251 DefineProperty("sql.backslash.escapes", &OptionsSQL::sqlBackslashEscapes,
252 "Enables backslash as an escape character in SQL.");
254 DefineProperty("lexer.sql.allow.dotted.word", &OptionsSQL::sqlAllowDottedWord,
255 "Set to 1 to colourise recognized words with dots "
256 "(recommended for Oracle PL/SQL objects).");
258 DefineWordListSets(sqlWordListDesc);
262 class LexerSQL : public ILexer {
263 public :
264 LexerSQL() {}
266 virtual ~LexerSQL() {}
268 int SCI_METHOD Version () const {
269 return lvOriginal;
272 void SCI_METHOD Release() {
273 delete this;
276 const char * SCI_METHOD PropertyNames() {
277 return osSQL.PropertyNames();
280 int SCI_METHOD PropertyType(const char *name) {
281 return osSQL.PropertyType(name);
284 const char * SCI_METHOD DescribeProperty(const char *name) {
285 return osSQL.DescribeProperty(name);
288 int SCI_METHOD PropertySet(const char *key, const char *val) {
289 if (osSQL.PropertySet(&options, key, val)) {
290 return 0;
292 return -1;
295 const char * SCI_METHOD DescribeWordListSets() {
296 return osSQL.DescribeWordListSets();
299 int SCI_METHOD WordListSet(int n, const char *wl);
300 void SCI_METHOD Lex (unsigned int startPos, int lengthDoc, int initStyle, IDocument *pAccess);
301 void SCI_METHOD Fold(unsigned int startPos, int lengthDoc, int initStyle, IDocument *pAccess);
303 void * SCI_METHOD PrivateCall(int, void *) {
304 return 0;
307 static ILexer *LexerFactorySQL() {
308 return new LexerSQL();
310 private:
311 bool IsStreamCommentStyle(int style) {
312 return style == SCE_SQL_COMMENT ||
313 style == SCE_SQL_COMMENTDOC ||
314 style == SCE_SQL_COMMENTDOCKEYWORD ||
315 style == SCE_SQL_COMMENTDOCKEYWORDERROR;
318 bool IsCommentStyle (int style) {
319 switch (style) {
320 case SCE_SQL_COMMENT :
321 case SCE_SQL_COMMENTDOC :
322 case SCE_SQL_COMMENTLINE :
323 case SCE_SQL_COMMENTLINEDOC :
324 case SCE_SQL_COMMENTDOCKEYWORD :
325 case SCE_SQL_COMMENTDOCKEYWORDERROR :
326 return true;
327 default :
328 return false;
332 bool IsCommentLine (int line, LexAccessor &styler) {
333 int pos = styler.LineStart(line);
334 int eol_pos = styler.LineStart(line + 1) - 1;
335 for (int i = pos; i + 1 < eol_pos; i++) {
336 int style = styler.StyleAt(i);
337 // MySQL needs -- comments to be followed by space or control char
338 if (style == SCE_SQL_COMMENTLINE && styler.Match(i, "--"))
339 return true;
340 else if (!IsASpaceOrTab(styler[i]))
341 return false;
343 return false;
346 OptionsSQL options;
347 OptionSetSQL osSQL;
348 SQLStates sqlStates;
350 WordList keywords1;
351 WordList keywords2;
352 WordList kw_pldoc;
353 WordList kw_sqlplus;
354 WordList kw_user1;
355 WordList kw_user2;
356 WordList kw_user3;
357 WordList kw_user4;
360 int SCI_METHOD LexerSQL::WordListSet(int n, const char *wl) {
361 WordList *wordListN = 0;
362 switch (n) {
363 case 0:
364 wordListN = &keywords1;
365 break;
366 case 1:
367 wordListN = &keywords2;
368 break;
369 case 2:
370 wordListN = &kw_pldoc;
371 break;
372 case 3:
373 wordListN = &kw_sqlplus;
374 break;
375 case 4:
376 wordListN = &kw_user1;
377 break;
378 case 5:
379 wordListN = &kw_user2;
380 break;
381 case 6:
382 wordListN = &kw_user3;
383 break;
384 case 7:
385 wordListN = &kw_user4;
387 int firstModification = -1;
388 if (wordListN) {
389 WordList wlNew;
390 wlNew.Set(wl);
391 if (*wordListN != wlNew) {
392 wordListN->Set(wl);
393 firstModification = 0;
396 return firstModification;
399 void SCI_METHOD LexerSQL::Lex(unsigned int startPos, int length, int initStyle, IDocument *pAccess) {
400 LexAccessor styler(pAccess);
401 StyleContext sc(startPos, length, initStyle, styler);
402 int styleBeforeDCKeyword = SCE_SQL_DEFAULT;
403 int offset = 0;
404 for (; sc.More(); sc.Forward(), offset++) {
405 // Determine if the current state should terminate.
406 switch (sc.state) {
407 case SCE_SQL_OPERATOR:
408 sc.SetState(SCE_SQL_DEFAULT);
409 break;
410 case SCE_SQL_NUMBER:
411 // We stop the number definition on non-numerical non-dot non-eE non-sign char
412 if (!IsANumberChar(sc.ch)) {
413 sc.SetState(SCE_SQL_DEFAULT);
415 break;
416 case SCE_SQL_IDENTIFIER:
417 if (!IsAWordChar(sc.ch, options.sqlAllowDottedWord)) {
418 int nextState = SCE_SQL_DEFAULT;
419 char s[1000];
420 sc.GetCurrentLowered(s, sizeof(s));
421 if (keywords1.InList(s)) {
422 sc.ChangeState(SCE_SQL_WORD);
423 } else if (keywords2.InList(s)) {
424 sc.ChangeState(SCE_SQL_WORD2);
425 } else if (kw_sqlplus.InListAbbreviated(s, '~')) {
426 sc.ChangeState(SCE_SQL_SQLPLUS);
427 if (strncmp(s, "rem", 3) == 0) {
428 nextState = SCE_SQL_SQLPLUS_COMMENT;
429 } else if (strncmp(s, "pro", 3) == 0) {
430 nextState = SCE_SQL_SQLPLUS_PROMPT;
432 } else if (kw_user1.InList(s)) {
433 sc.ChangeState(SCE_SQL_USER1);
434 } else if (kw_user2.InList(s)) {
435 sc.ChangeState(SCE_SQL_USER2);
436 } else if (kw_user3.InList(s)) {
437 sc.ChangeState(SCE_SQL_USER3);
438 } else if (kw_user4.InList(s)) {
439 sc.ChangeState(SCE_SQL_USER4);
441 sc.SetState(nextState);
443 break;
444 case SCE_SQL_QUOTEDIDENTIFIER:
445 if (sc.ch == 0x60) {
446 if (sc.chNext == 0x60) {
447 sc.Forward(); // Ignore it
448 } else {
449 sc.ForwardSetState(SCE_SQL_DEFAULT);
452 break;
453 case SCE_SQL_COMMENT:
454 if (sc.Match('*', '/')) {
455 sc.Forward();
456 sc.ForwardSetState(SCE_SQL_DEFAULT);
458 break;
459 case SCE_SQL_COMMENTDOC:
460 if (sc.Match('*', '/')) {
461 sc.Forward();
462 sc.ForwardSetState(SCE_SQL_DEFAULT);
463 } else if (sc.ch == '@' || sc.ch == '\\') { // Doxygen support
464 // Verify that we have the conditions to mark a comment-doc-keyword
465 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
466 styleBeforeDCKeyword = SCE_SQL_COMMENTDOC;
467 sc.SetState(SCE_SQL_COMMENTDOCKEYWORD);
470 break;
471 case SCE_SQL_COMMENTLINE:
472 case SCE_SQL_COMMENTLINEDOC:
473 case SCE_SQL_SQLPLUS_COMMENT:
474 case SCE_SQL_SQLPLUS_PROMPT:
475 if (sc.atLineStart) {
476 sc.SetState(SCE_SQL_DEFAULT);
478 break;
479 case SCE_SQL_COMMENTDOCKEYWORD:
480 if ((styleBeforeDCKeyword == SCE_SQL_COMMENTDOC) && sc.Match('*', '/')) {
481 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
482 sc.Forward();
483 sc.ForwardSetState(SCE_SQL_DEFAULT);
484 } else if (!IsADoxygenChar(sc.ch)) {
485 char s[100];
486 sc.GetCurrentLowered(s, sizeof(s));
487 if (!isspace(sc.ch) || !kw_pldoc.InList(s + 1)) {
488 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
490 sc.SetState(styleBeforeDCKeyword);
492 break;
493 case SCE_SQL_CHARACTER:
494 if (options.sqlBackslashEscapes && sc.ch == '\\') {
495 sc.Forward();
496 } else if (sc.ch == '\'') {
497 if (sc.chNext == '\"') {
498 sc.Forward();
499 } else {
500 sc.ForwardSetState(SCE_SQL_DEFAULT);
503 break;
504 case SCE_SQL_STRING:
505 if (sc.ch == '\\') {
506 // Escape sequence
507 sc.Forward();
508 } else if (sc.ch == '\"') {
509 if (sc.chNext == '\"') {
510 sc.Forward();
511 } else {
512 sc.ForwardSetState(SCE_SQL_DEFAULT);
515 break;
518 // Determine if a new state should be entered.
519 if (sc.state == SCE_SQL_DEFAULT) {
520 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
521 sc.SetState(SCE_SQL_NUMBER);
522 } else if (IsAWordStart(sc.ch)) {
523 sc.SetState(SCE_SQL_IDENTIFIER);
524 } else if (sc.ch == 0x60 && options.sqlBackticksIdentifier) {
525 sc.SetState(SCE_SQL_QUOTEDIDENTIFIER);
526 } else if (sc.Match('/', '*')) {
527 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Doxygen doc. style
528 sc.SetState(SCE_SQL_COMMENTDOC);
529 } else {
530 sc.SetState(SCE_SQL_COMMENT);
532 sc.Forward(); // Eat the * so it isn't used for the end of the comment
533 } else if (sc.Match('-', '-')) {
534 // MySQL requires a space or control char after --
535 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
536 // Perhaps we should enforce that with proper property:
537 //~ } else if (sc.Match("-- ")) {
538 sc.SetState(SCE_SQL_COMMENTLINE);
539 } else if (sc.ch == '#' && options.sqlNumbersignComment) {
540 sc.SetState(SCE_SQL_COMMENTLINEDOC);
541 } else if (sc.ch == '\'') {
542 sc.SetState(SCE_SQL_CHARACTER);
543 } else if (sc.ch == '\"') {
544 sc.SetState(SCE_SQL_STRING);
545 } else if (isoperator(static_cast<char>(sc.ch))) {
546 sc.SetState(SCE_SQL_OPERATOR);
550 sc.Complete();
553 void SCI_METHOD LexerSQL::Fold(unsigned int startPos, int length, int initStyle, IDocument *pAccess) {
554 if (!options.fold)
555 return;
556 LexAccessor styler(pAccess);
557 unsigned int endPos = startPos + length;
558 int visibleChars = 0;
559 int lineCurrent = styler.GetLine(startPos);
560 int levelCurrent = SC_FOLDLEVELBASE;
562 if (lineCurrent > 0) {
563 // Backtrack to previous line in case need to fix its fold status for folding block of single-line comments (i.e. '--').
564 lineCurrent -= 1;
565 startPos = styler.LineStart(lineCurrent);
567 if (lineCurrent > 0)
568 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
570 int levelNext = levelCurrent;
571 char chNext = styler[startPos];
572 int styleNext = styler.StyleAt(startPos);
573 int style = initStyle;
574 bool endFound = false;
575 bool isUnfoldingIgnored = false;
576 // this statementFound flag avoids to fold when the statement is on only one line by ignoring ELSE or ELSIF
577 // eg. "IF condition1 THEN ... ELSIF condition2 THEN ... ELSE ... END IF;"
578 bool statementFound = false;
579 unsigned short int sqlStatesCurrentLine = 0;
580 if (!options.foldOnlyBegin) {
581 sqlStatesCurrentLine = sqlStates.ForLine(lineCurrent);
583 for (unsigned int i = startPos; i < endPos; i++) {
584 char ch = chNext;
585 chNext = styler.SafeGetCharAt(i + 1);
586 int stylePrev = style;
587 style = styleNext;
588 styleNext = styler.StyleAt(i + 1);
589 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
590 if (atEOL || (!IsCommentStyle(style) && ch == ';')) {
591 if (endFound) {
592 //Maybe this is the end of "EXCEPTION" BLOCK (eg. "BEGIN ... EXCEPTION ... END;")
593 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, false);
595 // set endFound and isUnfoldingIgnored to false if EOL is reached or ';' is found
596 endFound = false;
597 isUnfoldingIgnored = false;
599 if ((!IsCommentStyle(style) && ch == ';')) {
600 if (sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)) {
601 // This is the end of "MERGE" statement.
602 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
603 levelNext--;
604 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, false);
605 levelNext--;
607 if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine))
608 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, false);
610 if (ch == ':' && chNext == '=' && !IsCommentStyle(style))
611 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);
613 if (options.foldComment && IsStreamCommentStyle(style)) {
614 if (!IsStreamCommentStyle(stylePrev)) {
615 levelNext++;
616 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
617 // Comments don't end at end of line and the next character may be unstyled.
618 levelNext--;
621 if (options.foldComment && (style == SCE_SQL_COMMENTLINE)) {
622 // MySQL needs -- comments to be followed by space or control char
623 if ((ch == '-') && (chNext == '-')) {
624 char chNext2 = styler.SafeGetCharAt(i + 2);
625 char chNext3 = styler.SafeGetCharAt(i + 3);
626 if (chNext2 == '{' || chNext3 == '{') {
627 levelNext++;
628 } else if (chNext2 == '}' || chNext3 == '}') {
629 levelNext--;
633 // Fold block of single-line comments (i.e. '--').
634 if (options.foldComment && atEOL && IsCommentLine(lineCurrent, styler)) {
635 if (!IsCommentLine(lineCurrent - 1, styler) && IsCommentLine(lineCurrent + 1, styler))
636 levelNext++;
637 else if (IsCommentLine(lineCurrent - 1, styler) && !IsCommentLine(lineCurrent + 1, styler))
638 levelNext--;
640 if (style == SCE_SQL_OPERATOR) {
641 if (ch == '(') {
642 if (levelCurrent > levelNext)
643 levelCurrent--;
644 levelNext++;
645 } else if (ch == ')') {
646 levelNext--;
647 } else if ((!options.foldOnlyBegin) && ch == ';') {
648 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, false);
651 // If new keyword (cannot trigger on elseif or nullif, does less tests)
652 if (style == SCE_SQL_WORD && stylePrev != SCE_SQL_WORD) {
653 const int MAX_KW_LEN = 9; // Maximum length of folding keywords
654 char s[MAX_KW_LEN + 2];
655 unsigned int j = 0;
656 for (; j < MAX_KW_LEN + 1; j++) {
657 if (!iswordchar(styler[i + j])) {
658 break;
660 s[j] = static_cast<char>(tolower(styler[i + j]));
662 if (j == MAX_KW_LEN + 1) {
663 // Keyword too long, don't test it
664 s[0] = '\0';
665 } else {
666 s[j] = '\0';
668 if (!options.foldOnlyBegin &&
669 strcmp(s, "select") == 0) {
670 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);
671 } else if (strcmp(s, "if") == 0) {
672 if (endFound) {
673 endFound = false;
674 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
675 // this end isn't for begin block, but for if block ("end if;")
676 // so ignore previous "end" by increment levelNext.
677 levelNext++;
679 } else {
680 if (!options.foldOnlyBegin)
681 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
682 if (levelCurrent > levelNext) {
683 // doesn't include this line into the folding block
684 // because doesn't hide IF (eg "END; IF")
685 levelCurrent = levelNext;
688 } else if (!options.foldOnlyBegin &&
689 strcmp(s, "then") == 0 &&
690 sqlStates.IsIntoCondition(sqlStatesCurrentLine)) {
691 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, false);
692 if (!options.foldOnlyBegin) {
693 if (levelCurrent > levelNext) {
694 levelCurrent = levelNext;
696 if (!statementFound)
697 levelNext++;
699 statementFound = true;
700 } else if (levelCurrent > levelNext) {
701 // doesn't include this line into the folding block
702 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
703 levelCurrent = levelNext;
705 } else if (strcmp(s, "loop") == 0 ||
706 strcmp(s, "case") == 0) {
707 if (endFound) {
708 endFound = false;
709 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
710 // this end isn't for begin block, but for loop block ("end loop;") or case block ("end case;")
711 // so ignore previous "end" by increment levelNext.
712 levelNext++;
714 if ((!options.foldOnlyBegin) && strcmp(s, "case") == 0) {
715 sqlStatesCurrentLine = sqlStates.EndCaseBlock(sqlStatesCurrentLine);
716 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
717 levelNext--; //again for the "end case;" and block when
719 } else if (!options.foldOnlyBegin) {
720 if (strcmp(s, "case") == 0) {
721 sqlStatesCurrentLine = sqlStates.BeginCaseBlock(sqlStatesCurrentLine);
722 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
725 if (levelCurrent > levelNext)
726 levelCurrent = levelNext;
728 if (!statementFound)
729 levelNext++;
731 statementFound = true;
732 } else if (levelCurrent > levelNext) {
733 // doesn't include this line into the folding block
734 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
735 levelCurrent = levelNext;
737 } else if ((!options.foldOnlyBegin) && (
738 // folding for ELSE and ELSIF block only if foldAtElse is set
739 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
740 options.foldAtElse && !statementFound) && strcmp(s, "elsif") == 0) {
741 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
742 levelCurrent--;
743 levelNext--;
744 } else if ((!options.foldOnlyBegin) && (
745 // folding for ELSE and ELSIF block only if foldAtElse is set
746 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
747 options.foldAtElse && !statementFound) && strcmp(s, "else") == 0) {
748 // prevent also ELSE is on the same line (eg. "ELSE ... END IF;")
749 statementFound = true;
750 if (sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) && sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
751 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
752 levelNext++;
753 } else {
754 // we are in same case "} ELSE {" in C language
755 levelCurrent--;
757 } else if (strcmp(s, "begin") == 0) {
758 levelNext++;
759 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, false);
760 } else if ((strcmp(s, "end") == 0) ||
761 // SQL Anywhere permits IF ... ELSE ... ENDIF
762 // will only be active if "endif" appears in the
763 // keyword list.
764 (strcmp(s, "endif") == 0)) {
765 endFound = true;
766 levelNext--;
767 if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine) && !sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
768 levelNext--;
769 if (levelNext < SC_FOLDLEVELBASE) {
770 levelNext = SC_FOLDLEVELBASE;
771 isUnfoldingIgnored = true;
773 } else if ((!options.foldOnlyBegin) &&
774 strcmp(s, "when") == 0 &&
775 !sqlStates.IsIgnoreWhen(sqlStatesCurrentLine) &&
776 !sqlStates.IsIntoExceptionBlock(sqlStatesCurrentLine) && (
777 sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) ||
778 sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)
781 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
783 // 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")
784 // and same way for MERGE statement.
785 if (!statementFound) {
786 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
787 levelCurrent--;
788 levelNext--;
790 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
792 } else if ((!options.foldOnlyBegin) && strcmp(s, "exit") == 0) {
793 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, true);
794 } else if ((!options.foldOnlyBegin) && !sqlStates.IsIntoDeclareBlock(sqlStatesCurrentLine) && strcmp(s, "exception") == 0) {
795 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, true);
796 } else if ((!options.foldOnlyBegin) &&
797 (strcmp(s, "declare") == 0 ||
798 strcmp(s, "function") == 0 ||
799 strcmp(s, "procedure") == 0 ||
800 strcmp(s, "package") == 0)) {
801 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, true);
802 } else if ((!options.foldOnlyBegin) &&
803 strcmp(s, "merge") == 0) {
804 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, true);
805 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
806 levelNext++;
807 statementFound = true;
810 if (atEOL) {
811 int levelUse = levelCurrent;
812 int lev = levelUse | levelNext << 16;
813 if (visibleChars == 0 && options.foldCompact)
814 lev |= SC_FOLDLEVELWHITEFLAG;
815 if (levelUse < levelNext)
816 lev |= SC_FOLDLEVELHEADERFLAG;
817 if (lev != styler.LevelAt(lineCurrent)) {
818 styler.SetLevel(lineCurrent, lev);
820 lineCurrent++;
821 levelCurrent = levelNext;
822 visibleChars = 0;
823 statementFound = false;
824 if (!options.foldOnlyBegin)
825 sqlStates.Set(lineCurrent, sqlStatesCurrentLine);
827 if (!isspacechar(ch)) {
828 visibleChars++;
833 LexerModule lmSQL(SCLEX_SQL, LexerSQL::LexerFactorySQL, "sql", sqlWordListDesc);