]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/lexers/LexSQL.cxx
Don't document wxSortedArrayString as deriving from wxArrayString.
[wxWidgets.git] / src / stc / scintilla / lexers / LexSQL.cxx
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 <vector>
19 #include <map>
20 #include <algorithm>
21
22 #include "ILexer.h"
23 #include "Scintilla.h"
24 #include "SciLexer.h"
25
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"
34
35 #ifdef SCI_NAMESPACE
36 using namespace Scintilla;
37 #endif
38
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 == '.');
44 }
45
46 static inline bool IsAWordStart(int ch) {
47 return (ch < 0x80) && (isalpha(ch) || ch == '_');
48 }
49
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 == ']');
55 }
56
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 == '+');
63 }
64
65
66 class SQLStates {
67 public :
68 void Set(int lineNumber, unsigned short int sqlStatesLine) {
69 sqlStatement.Set(lineNumber, sqlStatesLine);
70 }
71
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;
77
78 return sqlStatesLine;
79 }
80
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;
86
87 return sqlStatesLine;
88 }
89
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;
95
96 return sqlStatesLine;
97 }
98
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;
104
105 return sqlStatesLine;
106 }
107
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;
113
114 return sqlStatesLine;
115 }
116
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;
122
123 return sqlStatesLine;
124 }
125
126 unsigned short int IntoSelectStatement (unsigned short int sqlStatesLine, bool found) {
127 if (found)
128 sqlStatesLine |= MASK_INTO_SELECT_STATEMENT;
129 else
130 sqlStatesLine &= ~MASK_INTO_SELECT_STATEMENT;
131
132 return sqlStatesLine;
133 }
134
135 unsigned short int BeginCaseBlock (unsigned short int sqlStatesLine) {
136 if ((sqlStatesLine & MASK_NESTED_CASES) < MASK_NESTED_CASES) {
137 sqlStatesLine++;
138 }
139 return sqlStatesLine;
140 }
141
142 unsigned short int EndCaseBlock (unsigned short int sqlStatesLine) {
143 if ((sqlStatesLine & MASK_NESTED_CASES) > 0) {
144 sqlStatesLine--;
145 }
146 return sqlStatesLine;
147 }
148
149 bool IsIgnoreWhen (unsigned short int sqlStatesLine) {
150 return (sqlStatesLine & MASK_IGNORE_WHEN) != 0;
151 }
152
153 bool IsIntoCondition (unsigned short int sqlStatesLine) {
154 return (sqlStatesLine & MASK_INTO_CONDITION) != 0;
155 }
156
157 bool IsIntoCaseBlock (unsigned short int sqlStatesLine) {
158 return (sqlStatesLine & MASK_NESTED_CASES) != 0;
159 }
160
161 bool IsIntoExceptionBlock (unsigned short int sqlStatesLine) {
162 return (sqlStatesLine & MASK_INTO_EXCEPTION) != 0;
163 }
164
165 bool IsIntoSelectStatement (unsigned short int sqlStatesLine) {
166 return (sqlStatesLine & MASK_INTO_SELECT_STATEMENT) != 0;
167 }
168
169 bool IsCaseMergeWithoutWhenFound (unsigned short int sqlStatesLine) {
170 return (sqlStatesLine & MASK_CASE_MERGE_WITHOUT_WHEN_FOUND) != 0;
171 }
172
173 bool IsIntoDeclareBlock (unsigned short int sqlStatesLine) {
174 return (sqlStatesLine & MASK_INTO_DECLARE) != 0;
175 }
176
177 bool IsIntoMergeStatement (unsigned short int sqlStatesLine) {
178 return (sqlStatesLine & MASK_MERGE_STATEMENT) != 0;
179 }
180
181 unsigned short int ForLine(int lineNumber) {
182 return sqlStatement.ValueAt(lineNumber);
183 }
184
185 SQLStates() {}
186
187 private :
188 SparseState <unsigned short int> sqlStatement;
189 enum {
190 MASK_NESTED_CASES = 0x01FF,
191 MASK_INTO_SELECT_STATEMENT = 0x0200,
192 MASK_CASE_MERGE_WITHOUT_WHEN_FOUND = 0x0400,
193 MASK_MERGE_STATEMENT = 0x0800,
194 MASK_INTO_DECLARE = 0x1000,
195 MASK_INTO_EXCEPTION = 0x2000,
196 MASK_INTO_CONDITION = 0x4000,
197 MASK_IGNORE_WHEN = 0x8000
198 };
199 };
200
201 // Options used for LexerSQL
202 struct OptionsSQL {
203 bool fold;
204 bool foldAtElse;
205 bool foldComment;
206 bool foldCompact;
207 bool foldOnlyBegin;
208 bool sqlBackticksIdentifier;
209 bool sqlNumbersignComment;
210 bool sqlBackslashEscapes;
211 bool sqlAllowDottedWord;
212 OptionsSQL() {
213 fold = false;
214 foldAtElse = false;
215 foldComment = false;
216 foldCompact = false;
217 foldOnlyBegin = false;
218 sqlBackticksIdentifier = false;
219 sqlNumbersignComment = false;
220 sqlBackslashEscapes = false;
221 sqlAllowDottedWord = false;
222 }
223 };
224
225 static const char * const sqlWordListDesc[] = {
226 "Keywords",
227 "Database Objects",
228 "PLDoc",
229 "SQL*Plus",
230 "User Keywords 1",
231 "User Keywords 2",
232 "User Keywords 3",
233 "User Keywords 4",
234 0
235 };
236
237 struct OptionSetSQL : public OptionSet<OptionsSQL> {
238 OptionSetSQL() {
239 DefineProperty("fold", &OptionsSQL::fold);
240
241 DefineProperty("fold.sql.at.else", &OptionsSQL::foldAtElse,
242 "This option enables SQL folding on a \"ELSE\" and \"ELSIF\" line of an IF statement.");
243
244 DefineProperty("fold.comment", &OptionsSQL::foldComment);
245
246 DefineProperty("fold.compact", &OptionsSQL::foldCompact);
247
248 DefineProperty("fold.sql.only.begin", &OptionsSQL::foldOnlyBegin);
249
250 DefineProperty("lexer.sql.backticks.identifier", &OptionsSQL::sqlBackticksIdentifier);
251
252 DefineProperty("lexer.sql.numbersign.comment", &OptionsSQL::sqlNumbersignComment,
253 "If \"lexer.sql.numbersign.comment\" property is set to 0 a line beginning with '#' will not be a comment.");
254
255 DefineProperty("sql.backslash.escapes", &OptionsSQL::sqlBackslashEscapes,
256 "Enables backslash as an escape character in SQL.");
257
258 DefineProperty("lexer.sql.allow.dotted.word", &OptionsSQL::sqlAllowDottedWord,
259 "Set to 1 to colourise recognized words with dots "
260 "(recommended for Oracle PL/SQL objects).");
261
262 DefineWordListSets(sqlWordListDesc);
263 }
264 };
265
266 class LexerSQL : public ILexer {
267 public :
268 LexerSQL() {}
269
270 virtual ~LexerSQL() {}
271
272 int SCI_METHOD Version () const {
273 return lvOriginal;
274 }
275
276 void SCI_METHOD Release() {
277 delete this;
278 }
279
280 const char * SCI_METHOD PropertyNames() {
281 return osSQL.PropertyNames();
282 }
283
284 int SCI_METHOD PropertyType(const char *name) {
285 return osSQL.PropertyType(name);
286 }
287
288 const char * SCI_METHOD DescribeProperty(const char *name) {
289 return osSQL.DescribeProperty(name);
290 }
291
292 int SCI_METHOD PropertySet(const char *key, const char *val) {
293 if (osSQL.PropertySet(&options, key, val)) {
294 return 0;
295 }
296 return -1;
297 }
298
299 const char * SCI_METHOD DescribeWordListSets() {
300 return osSQL.DescribeWordListSets();
301 }
302
303 int SCI_METHOD WordListSet(int n, const char *wl);
304 void SCI_METHOD Lex (unsigned int startPos, int lengthDoc, int initStyle, IDocument *pAccess);
305 void SCI_METHOD Fold(unsigned int startPos, int lengthDoc, int initStyle, IDocument *pAccess);
306
307 void * SCI_METHOD PrivateCall(int, void *) {
308 return 0;
309 }
310
311 static ILexer *LexerFactorySQL() {
312 return new LexerSQL();
313 }
314 private:
315 bool IsStreamCommentStyle(int style) {
316 return style == SCE_SQL_COMMENT ||
317 style == SCE_SQL_COMMENTDOC ||
318 style == SCE_SQL_COMMENTDOCKEYWORD ||
319 style == SCE_SQL_COMMENTDOCKEYWORDERROR;
320 }
321
322 bool IsCommentStyle (int style) {
323 switch (style) {
324 case SCE_SQL_COMMENT :
325 case SCE_SQL_COMMENTDOC :
326 case SCE_SQL_COMMENTLINE :
327 case SCE_SQL_COMMENTLINEDOC :
328 case SCE_SQL_COMMENTDOCKEYWORD :
329 case SCE_SQL_COMMENTDOCKEYWORDERROR :
330 return true;
331 default :
332 return false;
333 }
334 }
335
336 bool IsCommentLine (int line, LexAccessor &styler) {
337 int pos = styler.LineStart(line);
338 int eol_pos = styler.LineStart(line + 1) - 1;
339 for (int i = pos; i + 1 < eol_pos; i++) {
340 int style = styler.StyleAt(i);
341 // MySQL needs -- comments to be followed by space or control char
342 if (style == SCE_SQL_COMMENTLINE && styler.Match(i, "--"))
343 return true;
344 else if (!IsASpaceOrTab(styler[i]))
345 return false;
346 }
347 return false;
348 }
349
350 OptionsSQL options;
351 OptionSetSQL osSQL;
352 SQLStates sqlStates;
353
354 WordList keywords1;
355 WordList keywords2;
356 WordList kw_pldoc;
357 WordList kw_sqlplus;
358 WordList kw_user1;
359 WordList kw_user2;
360 WordList kw_user3;
361 WordList kw_user4;
362 };
363
364 int SCI_METHOD LexerSQL::WordListSet(int n, const char *wl) {
365 WordList *wordListN = 0;
366 switch (n) {
367 case 0:
368 wordListN = &keywords1;
369 break;
370 case 1:
371 wordListN = &keywords2;
372 break;
373 case 2:
374 wordListN = &kw_pldoc;
375 break;
376 case 3:
377 wordListN = &kw_sqlplus;
378 break;
379 case 4:
380 wordListN = &kw_user1;
381 break;
382 case 5:
383 wordListN = &kw_user2;
384 break;
385 case 6:
386 wordListN = &kw_user3;
387 break;
388 case 7:
389 wordListN = &kw_user4;
390 }
391 int firstModification = -1;
392 if (wordListN) {
393 WordList wlNew;
394 wlNew.Set(wl);
395 if (*wordListN != wlNew) {
396 wordListN->Set(wl);
397 firstModification = 0;
398 }
399 }
400 return firstModification;
401 }
402
403 void SCI_METHOD LexerSQL::Lex(unsigned int startPos, int length, int initStyle, IDocument *pAccess) {
404 LexAccessor styler(pAccess);
405 StyleContext sc(startPos, length, initStyle, styler);
406 int styleBeforeDCKeyword = SCE_SQL_DEFAULT;
407 int offset = 0;
408 for (; sc.More(); sc.Forward(), offset++) {
409 // Determine if the current state should terminate.
410 switch (sc.state) {
411 case SCE_SQL_OPERATOR:
412 sc.SetState(SCE_SQL_DEFAULT);
413 break;
414 case SCE_SQL_NUMBER:
415 // We stop the number definition on non-numerical non-dot non-eE non-sign char
416 if (!IsANumberChar(sc.ch)) {
417 sc.SetState(SCE_SQL_DEFAULT);
418 }
419 break;
420 case SCE_SQL_IDENTIFIER:
421 if (!IsAWordChar(sc.ch, options.sqlAllowDottedWord)) {
422 int nextState = SCE_SQL_DEFAULT;
423 char s[1000];
424 sc.GetCurrentLowered(s, sizeof(s));
425 if (keywords1.InList(s)) {
426 sc.ChangeState(SCE_SQL_WORD);
427 } else if (keywords2.InList(s)) {
428 sc.ChangeState(SCE_SQL_WORD2);
429 } else if (kw_sqlplus.InListAbbreviated(s, '~')) {
430 sc.ChangeState(SCE_SQL_SQLPLUS);
431 if (strncmp(s, "rem", 3) == 0) {
432 nextState = SCE_SQL_SQLPLUS_COMMENT;
433 } else if (strncmp(s, "pro", 3) == 0) {
434 nextState = SCE_SQL_SQLPLUS_PROMPT;
435 }
436 } else if (kw_user1.InList(s)) {
437 sc.ChangeState(SCE_SQL_USER1);
438 } else if (kw_user2.InList(s)) {
439 sc.ChangeState(SCE_SQL_USER2);
440 } else if (kw_user3.InList(s)) {
441 sc.ChangeState(SCE_SQL_USER3);
442 } else if (kw_user4.InList(s)) {
443 sc.ChangeState(SCE_SQL_USER4);
444 }
445 sc.SetState(nextState);
446 }
447 break;
448 case SCE_SQL_QUOTEDIDENTIFIER:
449 if (sc.ch == 0x60) {
450 if (sc.chNext == 0x60) {
451 sc.Forward(); // Ignore it
452 } else {
453 sc.ForwardSetState(SCE_SQL_DEFAULT);
454 }
455 }
456 break;
457 case SCE_SQL_COMMENT:
458 if (sc.Match('*', '/')) {
459 sc.Forward();
460 sc.ForwardSetState(SCE_SQL_DEFAULT);
461 }
462 break;
463 case SCE_SQL_COMMENTDOC:
464 if (sc.Match('*', '/')) {
465 sc.Forward();
466 sc.ForwardSetState(SCE_SQL_DEFAULT);
467 } else if (sc.ch == '@' || sc.ch == '\\') { // Doxygen support
468 // Verify that we have the conditions to mark a comment-doc-keyword
469 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
470 styleBeforeDCKeyword = SCE_SQL_COMMENTDOC;
471 sc.SetState(SCE_SQL_COMMENTDOCKEYWORD);
472 }
473 }
474 break;
475 case SCE_SQL_COMMENTLINE:
476 case SCE_SQL_COMMENTLINEDOC:
477 case SCE_SQL_SQLPLUS_COMMENT:
478 case SCE_SQL_SQLPLUS_PROMPT:
479 if (sc.atLineStart) {
480 sc.SetState(SCE_SQL_DEFAULT);
481 }
482 break;
483 case SCE_SQL_COMMENTDOCKEYWORD:
484 if ((styleBeforeDCKeyword == SCE_SQL_COMMENTDOC) && sc.Match('*', '/')) {
485 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
486 sc.Forward();
487 sc.ForwardSetState(SCE_SQL_DEFAULT);
488 } else if (!IsADoxygenChar(sc.ch)) {
489 char s[100];
490 sc.GetCurrentLowered(s, sizeof(s));
491 if (!isspace(sc.ch) || !kw_pldoc.InList(s + 1)) {
492 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
493 }
494 sc.SetState(styleBeforeDCKeyword);
495 }
496 break;
497 case SCE_SQL_CHARACTER:
498 if (options.sqlBackslashEscapes && sc.ch == '\\') {
499 sc.Forward();
500 } else if (sc.ch == '\'') {
501 if (sc.chNext == '\"') {
502 sc.Forward();
503 } else {
504 sc.ForwardSetState(SCE_SQL_DEFAULT);
505 }
506 }
507 break;
508 case SCE_SQL_STRING:
509 if (sc.ch == '\\') {
510 // Escape sequence
511 sc.Forward();
512 } else if (sc.ch == '\"') {
513 if (sc.chNext == '\"') {
514 sc.Forward();
515 } else {
516 sc.ForwardSetState(SCE_SQL_DEFAULT);
517 }
518 }
519 break;
520 }
521
522 // Determine if a new state should be entered.
523 if (sc.state == SCE_SQL_DEFAULT) {
524 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
525 sc.SetState(SCE_SQL_NUMBER);
526 } else if (IsAWordStart(sc.ch)) {
527 sc.SetState(SCE_SQL_IDENTIFIER);
528 } else if (sc.ch == 0x60 && options.sqlBackticksIdentifier) {
529 sc.SetState(SCE_SQL_QUOTEDIDENTIFIER);
530 } else if (sc.Match('/', '*')) {
531 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Doxygen doc. style
532 sc.SetState(SCE_SQL_COMMENTDOC);
533 } else {
534 sc.SetState(SCE_SQL_COMMENT);
535 }
536 sc.Forward(); // Eat the * so it isn't used for the end of the comment
537 } else if (sc.Match('-', '-')) {
538 // MySQL requires a space or control char after --
539 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
540 // Perhaps we should enforce that with proper property:
541 //~ } else if (sc.Match("-- ")) {
542 sc.SetState(SCE_SQL_COMMENTLINE);
543 } else if (sc.ch == '#' && options.sqlNumbersignComment) {
544 sc.SetState(SCE_SQL_COMMENTLINEDOC);
545 } else if (sc.ch == '\'') {
546 sc.SetState(SCE_SQL_CHARACTER);
547 } else if (sc.ch == '\"') {
548 sc.SetState(SCE_SQL_STRING);
549 } else if (isoperator(static_cast<char>(sc.ch))) {
550 sc.SetState(SCE_SQL_OPERATOR);
551 }
552 }
553 }
554 sc.Complete();
555 }
556
557 void SCI_METHOD LexerSQL::Fold(unsigned int startPos, int length, int initStyle, IDocument *pAccess) {
558 if (!options.fold)
559 return;
560 LexAccessor styler(pAccess);
561 unsigned int endPos = startPos + length;
562 int visibleChars = 0;
563 int lineCurrent = styler.GetLine(startPos);
564 int levelCurrent = SC_FOLDLEVELBASE;
565
566 if (lineCurrent > 0) {
567 // Backtrack to previous line in case need to fix its fold status for folding block of single-line comments (i.e. '--').
568 lineCurrent -= 1;
569 startPos = styler.LineStart(lineCurrent);
570
571 if (lineCurrent > 0)
572 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
573 }
574 int levelNext = levelCurrent;
575 char chNext = styler[startPos];
576 int styleNext = styler.StyleAt(startPos);
577 int style = initStyle;
578 bool endFound = false;
579 bool isUnfoldingIgnored = false;
580 // this statementFound flag avoids to fold when the statement is on only one line by ignoring ELSE or ELSIF
581 // eg. "IF condition1 THEN ... ELSIF condition2 THEN ... ELSE ... END IF;"
582 bool statementFound = false;
583 unsigned short int sqlStatesCurrentLine = 0;
584 if (!options.foldOnlyBegin) {
585 sqlStatesCurrentLine = sqlStates.ForLine(lineCurrent);
586 }
587 for (unsigned int i = startPos; i < endPos; i++) {
588 char ch = chNext;
589 chNext = styler.SafeGetCharAt(i + 1);
590 int stylePrev = style;
591 style = styleNext;
592 styleNext = styler.StyleAt(i + 1);
593 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
594 if (atEOL || (!IsCommentStyle(style) && ch == ';')) {
595 if (endFound) {
596 //Maybe this is the end of "EXCEPTION" BLOCK (eg. "BEGIN ... EXCEPTION ... END;")
597 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, false);
598 }
599 // set endFound and isUnfoldingIgnored to false if EOL is reached or ';' is found
600 endFound = false;
601 isUnfoldingIgnored = false;
602 }
603 if ((!IsCommentStyle(style) && ch == ';')) {
604 if (sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)) {
605 // This is the end of "MERGE" statement.
606 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
607 levelNext--;
608 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, false);
609 levelNext--;
610 }
611 if (sqlStates.IsIntoSelectStatement(sqlStatesCurrentLine))
612 sqlStatesCurrentLine = sqlStates.IntoSelectStatement(sqlStatesCurrentLine, false);
613 }
614 if (options.foldComment && IsStreamCommentStyle(style)) {
615 if (!IsStreamCommentStyle(stylePrev)) {
616 levelNext++;
617 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
618 // Comments don't end at end of line and the next character may be unstyled.
619 levelNext--;
620 }
621 }
622 if (options.foldComment && (style == SCE_SQL_COMMENTLINE)) {
623 // MySQL needs -- comments to be followed by space or control char
624 if ((ch == '-') && (chNext == '-')) {
625 char chNext2 = styler.SafeGetCharAt(i + 2);
626 char chNext3 = styler.SafeGetCharAt(i + 3);
627 if (chNext2 == '{' || chNext3 == '{') {
628 levelNext++;
629 } else if (chNext2 == '}' || chNext3 == '}') {
630 levelNext--;
631 }
632 }
633 }
634 // Fold block of single-line comments (i.e. '--').
635 if (options.foldComment && atEOL && IsCommentLine(lineCurrent, styler)) {
636 if (!IsCommentLine(lineCurrent - 1, styler) && IsCommentLine(lineCurrent + 1, styler))
637 levelNext++;
638 else if (IsCommentLine(lineCurrent - 1, styler) && !IsCommentLine(lineCurrent + 1, styler))
639 levelNext--;
640 }
641 if (style == SCE_SQL_OPERATOR) {
642 if (ch == '(') {
643 if (levelCurrent > levelNext)
644 levelCurrent--;
645 levelNext++;
646 } else if (ch == ')') {
647 levelNext--;
648 } else if ((!options.foldOnlyBegin) && ch == ';') {
649 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, false);
650 }
651 }
652 // If new keyword (cannot trigger on elseif or nullif, does less tests)
653 if (style == SCE_SQL_WORD && stylePrev != SCE_SQL_WORD) {
654 const int MAX_KW_LEN = 9; // Maximum length of folding keywords
655 char s[MAX_KW_LEN + 2];
656 unsigned int j = 0;
657 for (; j < MAX_KW_LEN + 1; j++) {
658 if (!iswordchar(styler[i + j])) {
659 break;
660 }
661 s[j] = static_cast<char>(tolower(styler[i + j]));
662 }
663 if (j == MAX_KW_LEN + 1) {
664 // Keyword too long, don't test it
665 s[0] = '\0';
666 } else {
667 s[j] = '\0';
668 }
669
670 if (!options.foldOnlyBegin &&
671 strcmp(s, "select") == 0) {
672 sqlStatesCurrentLine = sqlStates.IntoSelectStatement(sqlStatesCurrentLine, true);
673 } else if (strcmp(s, "if") == 0) {
674 if (endFound) {
675 endFound = false;
676 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
677 // this end isn't for begin block, but for if block ("end if;")
678 // so ignore previous "end" by increment levelNext.
679 levelNext++;
680 }
681 } else {
682 if (!options.foldOnlyBegin)
683 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
684 if (levelCurrent > levelNext) {
685 // doesn't include this line into the folding block
686 // because doesn't hide IF (eg "END; IF")
687 levelCurrent = levelNext;
688 }
689 }
690 } else if (!options.foldOnlyBegin &&
691 strcmp(s, "then") == 0 &&
692 sqlStates.IsIntoCondition(sqlStatesCurrentLine)) {
693 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, false);
694 if (!options.foldOnlyBegin) {
695 if (levelCurrent > levelNext) {
696 levelCurrent = levelNext;
697 }
698 if (!statementFound)
699 levelNext++;
700
701 statementFound = true;
702 } else if (levelCurrent > levelNext) {
703 // doesn't include this line into the folding block
704 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
705 levelCurrent = levelNext;
706 }
707 } else if (strcmp(s, "loop") == 0 ||
708 strcmp(s, "case") == 0) {
709 if (endFound) {
710 endFound = false;
711 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
712 // this end isn't for begin block, but for loop block ("end loop;") or case block ("end case;")
713 // so ignore previous "end" by increment levelNext.
714 levelNext++;
715 }
716 if ((!options.foldOnlyBegin) && strcmp(s, "case") == 0) {
717 sqlStatesCurrentLine = sqlStates.EndCaseBlock(sqlStatesCurrentLine);
718 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
719 levelNext--; //again for the "end case;" and block when
720 }
721 } else if (!options.foldOnlyBegin) {
722 if (strcmp(s, "case") == 0)
723 sqlStatesCurrentLine = sqlStates.BeginCaseBlock(sqlStatesCurrentLine);
724
725 if (levelCurrent > levelNext)
726 levelCurrent = levelNext;
727
728 if (!statementFound)
729 levelNext++;
730
731 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
732 statementFound = true;
733 } else if (levelCurrent > levelNext) {
734 // doesn't include this line into the folding block
735 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
736 levelCurrent = levelNext;
737 }
738 } else if ((!options.foldOnlyBegin) && (
739 // folding for ELSE and ELSIF block only if foldAtElse is set
740 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
741 options.foldAtElse && !statementFound) && strcmp(s, "elsif") == 0) {
742 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
743 levelCurrent--;
744 levelNext--;
745 } else if ((!options.foldOnlyBegin) && (
746 // folding for ELSE and ELSIF block only if foldAtElse is set
747 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
748 options.foldAtElse && !statementFound) && strcmp(s, "else") == 0) {
749 // prevent also ELSE is on the same line (eg. "ELSE ... END IF;")
750 statementFound = true;
751 if (sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) && sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
752 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
753 levelNext++;
754 } else {
755 // we are in same case "} ELSE {" in C language
756 levelCurrent--;
757 }
758 } else if (strcmp(s, "begin") == 0) {
759 levelNext++;
760 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, false);
761 } else if ((strcmp(s, "end") == 0) ||
762 // SQL Anywhere permits IF ... ELSE ... ENDIF
763 // will only be active if "endif" appears in the
764 // keyword list.
765 (strcmp(s, "endif") == 0)) {
766 endFound = true;
767 levelNext--;
768 if (sqlStates.IsIntoSelectStatement(sqlStatesCurrentLine) && !sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
769 levelNext--;
770 if (levelNext < SC_FOLDLEVELBASE) {
771 levelNext = SC_FOLDLEVELBASE;
772 isUnfoldingIgnored = true;
773 }
774 } else if ((!options.foldOnlyBegin) &&
775 strcmp(s, "when") == 0 &&
776 !sqlStates.IsIgnoreWhen(sqlStatesCurrentLine) &&
777 !sqlStates.IsIntoExceptionBlock(sqlStatesCurrentLine) && (
778 sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) ||
779 sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)
780 )
781 ) {
782 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
783
784 // 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")
785 // and same way for MERGE statement.
786 if (!statementFound) {
787 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
788 levelCurrent--;
789 levelNext--;
790 }
791 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
792 }
793 } else if ((!options.foldOnlyBegin) && strcmp(s, "exit") == 0) {
794 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, true);
795 } else if ((!options.foldOnlyBegin) && !sqlStates.IsIntoDeclareBlock(sqlStatesCurrentLine) && strcmp(s, "exception") == 0) {
796 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, true);
797 } else if ((!options.foldOnlyBegin) &&
798 (strcmp(s, "declare") == 0 ||
799 strcmp(s, "function") == 0 ||
800 strcmp(s, "procedure") == 0 ||
801 strcmp(s, "package") == 0)) {
802 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, true);
803 } else if ((!options.foldOnlyBegin) &&
804 strcmp(s, "merge") == 0) {
805 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, true);
806 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
807 levelNext++;
808 statementFound = true;
809 }
810 }
811 if (atEOL) {
812 int levelUse = levelCurrent;
813 int lev = levelUse | levelNext << 16;
814 if (visibleChars == 0 && options.foldCompact)
815 lev |= SC_FOLDLEVELWHITEFLAG;
816 if (levelUse < levelNext)
817 lev |= SC_FOLDLEVELHEADERFLAG;
818 if (lev != styler.LevelAt(lineCurrent)) {
819 styler.SetLevel(lineCurrent, lev);
820 }
821 lineCurrent++;
822 levelCurrent = levelNext;
823 visibleChars = 0;
824 statementFound = false;
825 if (!options.foldOnlyBegin)
826 sqlStates.Set(lineCurrent, sqlStatesCurrentLine);
827 }
828 if (!isspacechar(ch)) {
829 visibleChars++;
830 }
831 }
832 }
833
834 LexerModule lmSQL(SCLEX_SQL, LexerSQL::LexerFactorySQL, "sql", sqlWordListDesc);