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