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() : DefaultLexer("sql", SCLEX_SQL) {}
307
~LexerSQL()308 virtual ~LexerSQL() {}
309
Version() const310 int SCI_METHOD Version () const override {
311 return lvIdentity;
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
PropertyGet(const char * key)337 const char * SCI_METHOD PropertyGet(const char *key) override {
338 return osSQL.PropertyGet(key);
339 }
340
DescribeWordListSets()341 const char * SCI_METHOD DescribeWordListSets() override {
342 return osSQL.DescribeWordListSets();
343 }
344
345 Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override;
346 void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
347 void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override;
348
PrivateCall(int,void *)349 void * SCI_METHOD PrivateCall(int, void *) override {
350 return 0;
351 }
352
LexerFactorySQL()353 static ILexer *LexerFactorySQL() {
354 return new LexerSQL();
355 }
356 private:
IsStreamCommentStyle(int style)357 bool IsStreamCommentStyle(int style) {
358 return style == SCE_SQL_COMMENT ||
359 style == SCE_SQL_COMMENTDOC ||
360 style == SCE_SQL_COMMENTDOCKEYWORD ||
361 style == SCE_SQL_COMMENTDOCKEYWORDERROR;
362 }
363
IsCommentStyle(int style)364 bool IsCommentStyle (int style) {
365 switch (style) {
366 case SCE_SQL_COMMENT :
367 case SCE_SQL_COMMENTDOC :
368 case SCE_SQL_COMMENTLINE :
369 case SCE_SQL_COMMENTLINEDOC :
370 case SCE_SQL_COMMENTDOCKEYWORD :
371 case SCE_SQL_COMMENTDOCKEYWORDERROR :
372 return true;
373 default :
374 return false;
375 }
376 }
377
IsCommentLine(Sci_Position line,LexAccessor & styler)378 bool IsCommentLine (Sci_Position line, LexAccessor &styler) {
379 Sci_Position pos = styler.LineStart(line);
380 Sci_Position eol_pos = styler.LineStart(line + 1) - 1;
381 for (Sci_Position i = pos; i + 1 < eol_pos; i++) {
382 int style = styler.StyleAt(i);
383 // MySQL needs -- comments to be followed by space or control char
384 if (style == SCE_SQL_COMMENTLINE && styler.Match(i, "--"))
385 return true;
386 else if (!IsASpaceOrTab(styler[i]))
387 return false;
388 }
389 return false;
390 }
391
392 OptionsSQL options;
393 OptionSetSQL osSQL;
394 SQLStates sqlStates;
395
396 WordList keywords1;
397 WordList keywords2;
398 WordList kw_pldoc;
399 WordList kw_sqlplus;
400 WordList kw_user1;
401 WordList kw_user2;
402 WordList kw_user3;
403 WordList kw_user4;
404 };
405
WordListSet(int n,const char * wl)406 Sci_Position SCI_METHOD LexerSQL::WordListSet(int n, const char *wl) {
407 WordList *wordListN = 0;
408 switch (n) {
409 case 0:
410 wordListN = &keywords1;
411 break;
412 case 1:
413 wordListN = &keywords2;
414 break;
415 case 2:
416 wordListN = &kw_pldoc;
417 break;
418 case 3:
419 wordListN = &kw_sqlplus;
420 break;
421 case 4:
422 wordListN = &kw_user1;
423 break;
424 case 5:
425 wordListN = &kw_user2;
426 break;
427 case 6:
428 wordListN = &kw_user3;
429 break;
430 case 7:
431 wordListN = &kw_user4;
432 }
433 Sci_Position firstModification = -1;
434 if (wordListN) {
435 WordList wlNew;
436 wlNew.Set(wl);
437 if (*wordListN != wlNew) {
438 wordListN->Set(wl);
439 firstModification = 0;
440 }
441 }
442 return firstModification;
443 }
444
Lex(Sci_PositionU startPos,Sci_Position length,int initStyle,IDocument * pAccess)445 void SCI_METHOD LexerSQL::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
446 LexAccessor styler(pAccess);
447 StyleContext sc(startPos, length, initStyle, styler);
448 int styleBeforeDCKeyword = SCE_SQL_DEFAULT;
449
450 for (; sc.More(); sc.Forward()) {
451 // Determine if the current state should terminate.
452 switch (sc.state) {
453 case SCE_SQL_OPERATOR:
454 sc.SetState(SCE_SQL_DEFAULT);
455 break;
456 case SCE_SQL_NUMBER:
457 // We stop the number definition on non-numerical non-dot non-eE non-sign char
458 if (!IsANumberChar(sc.ch, sc.chPrev)) {
459 sc.SetState(SCE_SQL_DEFAULT);
460 }
461 break;
462 case SCE_SQL_IDENTIFIER:
463 if (!IsAWordChar(sc.ch, options.sqlAllowDottedWord)) {
464 int nextState = SCE_SQL_DEFAULT;
465 char s[1000];
466 sc.GetCurrentLowered(s, sizeof(s));
467 if (keywords1.InList(s)) {
468 sc.ChangeState(SCE_SQL_WORD);
469 } else if (keywords2.InList(s)) {
470 sc.ChangeState(SCE_SQL_WORD2);
471 } else if (kw_sqlplus.InListAbbreviated(s, '~')) {
472 sc.ChangeState(SCE_SQL_SQLPLUS);
473 if (strncmp(s, "rem", 3) == 0) {
474 nextState = SCE_SQL_SQLPLUS_COMMENT;
475 } else if (strncmp(s, "pro", 3) == 0) {
476 nextState = SCE_SQL_SQLPLUS_PROMPT;
477 }
478 } else if (kw_user1.InList(s)) {
479 sc.ChangeState(SCE_SQL_USER1);
480 } else if (kw_user2.InList(s)) {
481 sc.ChangeState(SCE_SQL_USER2);
482 } else if (kw_user3.InList(s)) {
483 sc.ChangeState(SCE_SQL_USER3);
484 } else if (kw_user4.InList(s)) {
485 sc.ChangeState(SCE_SQL_USER4);
486 }
487 sc.SetState(nextState);
488 }
489 break;
490 case SCE_SQL_QUOTEDIDENTIFIER:
491 if (sc.ch == 0x60) {
492 if (sc.chNext == 0x60) {
493 sc.Forward(); // Ignore it
494 } else {
495 sc.ForwardSetState(SCE_SQL_DEFAULT);
496 }
497 }
498 break;
499 case SCE_SQL_COMMENT:
500 if (sc.Match('*', '/')) {
501 sc.Forward();
502 sc.ForwardSetState(SCE_SQL_DEFAULT);
503 }
504 break;
505 case SCE_SQL_COMMENTDOC:
506 if (sc.Match('*', '/')) {
507 sc.Forward();
508 sc.ForwardSetState(SCE_SQL_DEFAULT);
509 } else if (sc.ch == '@' || sc.ch == '\\') { // Doxygen support
510 // Verify that we have the conditions to mark a comment-doc-keyword
511 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
512 styleBeforeDCKeyword = SCE_SQL_COMMENTDOC;
513 sc.SetState(SCE_SQL_COMMENTDOCKEYWORD);
514 }
515 }
516 break;
517 case SCE_SQL_COMMENTLINE:
518 case SCE_SQL_COMMENTLINEDOC:
519 case SCE_SQL_SQLPLUS_COMMENT:
520 case SCE_SQL_SQLPLUS_PROMPT:
521 if (sc.atLineStart) {
522 sc.SetState(SCE_SQL_DEFAULT);
523 }
524 break;
525 case SCE_SQL_COMMENTDOCKEYWORD:
526 if ((styleBeforeDCKeyword == SCE_SQL_COMMENTDOC) && sc.Match('*', '/')) {
527 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
528 sc.Forward();
529 sc.ForwardSetState(SCE_SQL_DEFAULT);
530 } else if (!IsADoxygenChar(sc.ch)) {
531 char s[100];
532 sc.GetCurrentLowered(s, sizeof(s));
533 if (!isspace(sc.ch) || !kw_pldoc.InList(s + 1)) {
534 sc.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR);
535 }
536 sc.SetState(styleBeforeDCKeyword);
537 }
538 break;
539 case SCE_SQL_CHARACTER:
540 if (options.sqlBackslashEscapes && sc.ch == '\\') {
541 sc.Forward();
542 } else if (sc.ch == '\'') {
543 if (sc.chNext == '\'') {
544 sc.Forward();
545 } else {
546 sc.ForwardSetState(SCE_SQL_DEFAULT);
547 }
548 }
549 break;
550 case SCE_SQL_STRING:
551 if (options.sqlBackslashEscapes && sc.ch == '\\') {
552 // Escape sequence
553 sc.Forward();
554 } else if (sc.ch == '\"') {
555 if (sc.chNext == '\"') {
556 sc.Forward();
557 } else {
558 sc.ForwardSetState(SCE_SQL_DEFAULT);
559 }
560 }
561 break;
562 case SCE_SQL_QOPERATOR:
563 // Locate the unique Q operator character
564 sc.Complete();
565 char qOperator = 0x00;
566 for (Sci_Position styleStartPos = sc.currentPos; styleStartPos > 0; --styleStartPos) {
567 if (styler.StyleAt(styleStartPos - 1) != SCE_SQL_QOPERATOR) {
568 qOperator = styler.SafeGetCharAt(styleStartPos + 2);
569 break;
570 }
571 }
572
573 char qComplement = 0x00;
574
575 if (qOperator == '<') {
576 qComplement = '>';
577 } else if (qOperator == '(') {
578 qComplement = ')';
579 } else if (qOperator == '{') {
580 qComplement = '}';
581 } else if (qOperator == '[') {
582 qComplement = ']';
583 } else {
584 qComplement = qOperator;
585 }
586
587 if (sc.Match(qComplement, '\'')) {
588 sc.Forward();
589 sc.ForwardSetState(SCE_SQL_DEFAULT);
590 }
591 break;
592 }
593
594 // Determine if a new state should be entered.
595 if (sc.state == SCE_SQL_DEFAULT) {
596 if (sc.Match('q', '\'') || sc.Match('Q', '\'')) {
597 sc.SetState(SCE_SQL_QOPERATOR);
598 sc.Forward();
599 } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext)) ||
600 ((sc.ch == '-' || sc.ch == '+') && IsADigit(sc.chNext) && !IsADigit(sc.chPrev))) {
601 sc.SetState(SCE_SQL_NUMBER);
602 } else if (IsAWordStart(sc.ch)) {
603 sc.SetState(SCE_SQL_IDENTIFIER);
604 } else if (sc.ch == 0x60 && options.sqlBackticksIdentifier) {
605 sc.SetState(SCE_SQL_QUOTEDIDENTIFIER);
606 } else if (sc.Match('/', '*')) {
607 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Doxygen doc. style
608 sc.SetState(SCE_SQL_COMMENTDOC);
609 } else {
610 sc.SetState(SCE_SQL_COMMENT);
611 }
612 sc.Forward(); // Eat the * so it isn't used for the end of the comment
613 } else if (sc.Match('-', '-')) {
614 // MySQL requires a space or control char after --
615 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
616 // Perhaps we should enforce that with proper property:
617 //~ } else if (sc.Match("-- ")) {
618 sc.SetState(SCE_SQL_COMMENTLINE);
619 } else if (sc.ch == '#' && options.sqlNumbersignComment) {
620 sc.SetState(SCE_SQL_COMMENTLINEDOC);
621 } else if (sc.ch == '\'') {
622 sc.SetState(SCE_SQL_CHARACTER);
623 } else if (sc.ch == '\"') {
624 sc.SetState(SCE_SQL_STRING);
625 } else if (isoperator(static_cast<char>(sc.ch))) {
626 sc.SetState(SCE_SQL_OPERATOR);
627 }
628 }
629 }
630 sc.Complete();
631 }
632
Fold(Sci_PositionU startPos,Sci_Position length,int initStyle,IDocument * pAccess)633 void SCI_METHOD LexerSQL::Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
634 if (!options.fold)
635 return;
636 LexAccessor styler(pAccess);
637 Sci_PositionU endPos = startPos + length;
638 int visibleChars = 0;
639 Sci_Position lineCurrent = styler.GetLine(startPos);
640 int levelCurrent = SC_FOLDLEVELBASE;
641
642 if (lineCurrent > 0) {
643 // Backtrack to previous line in case need to fix its fold status for folding block of single-line comments (i.e. '--').
644 Sci_Position lastNLPos = -1;
645 // And keep going back until we find an operator ';' followed
646 // by white-space and/or comments. This will improve folding.
647 while (--startPos > 0) {
648 char ch = styler[startPos];
649 if (ch == '\n' || (ch == '\r' && styler[startPos + 1] != '\n')) {
650 lastNLPos = startPos;
651 } else if (ch == ';' &&
652 styler.StyleAt(startPos) == SCE_SQL_OPERATOR) {
653 bool isAllClear = true;
654 for (Sci_Position tempPos = startPos + 1;
655 tempPos < lastNLPos;
656 ++tempPos) {
657 int tempStyle = styler.StyleAt(tempPos);
658 if (!IsCommentStyle(tempStyle)
659 && tempStyle != SCE_SQL_DEFAULT) {
660 isAllClear = false;
661 break;
662 }
663 }
664 if (isAllClear) {
665 startPos = lastNLPos + 1;
666 break;
667 }
668 }
669 }
670 lineCurrent = styler.GetLine(startPos);
671 if (lineCurrent > 0)
672 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
673 }
674 // And because folding ends at ';', keep going until we find one
675 // Otherwise if create ... view ... as is split over multiple
676 // lines the folding won't always update immediately.
677 Sci_PositionU docLength = styler.Length();
678 for (; endPos < docLength; ++endPos) {
679 if (styler.SafeGetCharAt(endPos) == ';') {
680 break;
681 }
682 }
683
684 int levelNext = levelCurrent;
685 char chNext = styler[startPos];
686 int styleNext = styler.StyleAt(startPos);
687 int style = initStyle;
688 bool endFound = false;
689 bool isUnfoldingIgnored = false;
690 // this statementFound flag avoids to fold when the statement is on only one line by ignoring ELSE or ELSIF
691 // eg. "IF condition1 THEN ... ELSIF condition2 THEN ... ELSE ... END IF;"
692 bool statementFound = false;
693 sql_state_t sqlStatesCurrentLine = 0;
694 if (!options.foldOnlyBegin) {
695 sqlStatesCurrentLine = sqlStates.ForLine(lineCurrent);
696 }
697 for (Sci_PositionU i = startPos; i < endPos; i++) {
698 char ch = chNext;
699 chNext = styler.SafeGetCharAt(i + 1);
700 int stylePrev = style;
701 style = styleNext;
702 styleNext = styler.StyleAt(i + 1);
703 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
704 if (atEOL || (!IsCommentStyle(style) && ch == ';')) {
705 if (endFound) {
706 //Maybe this is the end of "EXCEPTION" BLOCK (eg. "BEGIN ... EXCEPTION ... END;")
707 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, false);
708 }
709 // set endFound and isUnfoldingIgnored to false if EOL is reached or ';' is found
710 endFound = false;
711 isUnfoldingIgnored = false;
712 }
713 if ((!IsCommentStyle(style) && ch == ';')) {
714 if (sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)) {
715 // This is the end of "MERGE" statement.
716 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
717 levelNext--;
718 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, false);
719 levelNext--;
720 }
721 if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine))
722 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, false);
723 if (sqlStates.IsIntoCreateStatement(sqlStatesCurrentLine)) {
724 if (sqlStates.IsIntoCreateViewStatement(sqlStatesCurrentLine)) {
725 if (sqlStates.IsIntoCreateViewAsStatement(sqlStatesCurrentLine)) {
726 levelNext--;
727 sqlStatesCurrentLine = sqlStates.IntoCreateViewAsStatement(sqlStatesCurrentLine, false);
728 }
729 sqlStatesCurrentLine = sqlStates.IntoCreateViewStatement(sqlStatesCurrentLine, false);
730 }
731 sqlStatesCurrentLine = sqlStates.IntoCreateStatement(sqlStatesCurrentLine, false);
732 }
733 }
734 if (ch == ':' && chNext == '=' && !IsCommentStyle(style))
735 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);
736
737 if (options.foldComment && IsStreamCommentStyle(style)) {
738 if (!IsStreamCommentStyle(stylePrev)) {
739 levelNext++;
740 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
741 // Comments don't end at end of line and the next character may be unstyled.
742 levelNext--;
743 }
744 }
745 if (options.foldComment && (style == SCE_SQL_COMMENTLINE)) {
746 // MySQL needs -- comments to be followed by space or control char
747 if ((ch == '-') && (chNext == '-')) {
748 char chNext2 = styler.SafeGetCharAt(i + 2);
749 char chNext3 = styler.SafeGetCharAt(i + 3);
750 if (chNext2 == '{' || chNext3 == '{') {
751 levelNext++;
752 } else if (chNext2 == '}' || chNext3 == '}') {
753 levelNext--;
754 }
755 }
756 }
757 // Fold block of single-line comments (i.e. '--').
758 if (options.foldComment && atEOL && IsCommentLine(lineCurrent, styler)) {
759 if (!IsCommentLine(lineCurrent - 1, styler) && IsCommentLine(lineCurrent + 1, styler))
760 levelNext++;
761 else if (IsCommentLine(lineCurrent - 1, styler) && !IsCommentLine(lineCurrent + 1, styler))
762 levelNext--;
763 }
764 if (style == SCE_SQL_OPERATOR) {
765 if (ch == '(') {
766 if (levelCurrent > levelNext)
767 levelCurrent--;
768 levelNext++;
769 } else if (ch == ')') {
770 levelNext--;
771 } else if ((!options.foldOnlyBegin) && ch == ';') {
772 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, false);
773 }
774 }
775 // If new keyword (cannot trigger on elseif or nullif, does less tests)
776 if (style == SCE_SQL_WORD && stylePrev != SCE_SQL_WORD) {
777 const int MAX_KW_LEN = 9; // Maximum length of folding keywords
778 char s[MAX_KW_LEN + 2];
779 unsigned int j = 0;
780 for (; j < MAX_KW_LEN + 1; j++) {
781 if (!iswordchar(styler[i + j])) {
782 break;
783 }
784 s[j] = static_cast<char>(tolower(styler[i + j]));
785 }
786 if (j == MAX_KW_LEN + 1) {
787 // Keyword too long, don't test it
788 s[0] = '\0';
789 } else {
790 s[j] = '\0';
791 }
792 if (!options.foldOnlyBegin &&
793 strcmp(s, "select") == 0) {
794 sqlStatesCurrentLine = sqlStates.IntoSelectStatementOrAssignment(sqlStatesCurrentLine, true);
795 } else if (strcmp(s, "if") == 0) {
796 if (endFound) {
797 endFound = false;
798 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
799 // this end isn't for begin block, but for if block ("end if;")
800 // so ignore previous "end" by increment levelNext.
801 levelNext++;
802 }
803 } else {
804 if (!options.foldOnlyBegin)
805 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
806 if (levelCurrent > levelNext) {
807 // doesn't include this line into the folding block
808 // because doesn't hide IF (eg "END; IF")
809 levelCurrent = levelNext;
810 }
811 }
812 } else if (!options.foldOnlyBegin &&
813 strcmp(s, "then") == 0 &&
814 sqlStates.IsIntoCondition(sqlStatesCurrentLine)) {
815 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, false);
816 if (!options.foldOnlyBegin) {
817 if (levelCurrent > levelNext) {
818 levelCurrent = levelNext;
819 }
820 if (!statementFound)
821 levelNext++;
822
823 statementFound = true;
824 } else if (levelCurrent > levelNext) {
825 // doesn't include this line into the folding block
826 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
827 levelCurrent = levelNext;
828 }
829 } else if (strcmp(s, "loop") == 0 ||
830 strcmp(s, "case") == 0) {
831 if (endFound) {
832 endFound = false;
833 if (options.foldOnlyBegin && !isUnfoldingIgnored) {
834 // this end isn't for begin block, but for loop block ("end loop;") or case block ("end case;")
835 // so ignore previous "end" by increment levelNext.
836 levelNext++;
837 }
838 if ((!options.foldOnlyBegin) && strcmp(s, "case") == 0) {
839 sqlStatesCurrentLine = sqlStates.EndCaseBlock(sqlStatesCurrentLine);
840 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
841 levelNext--; //again for the "end case;" and block when
842 }
843 } else if (!options.foldOnlyBegin) {
844 if (strcmp(s, "case") == 0) {
845 sqlStatesCurrentLine = sqlStates.BeginCaseBlock(sqlStatesCurrentLine);
846 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
847 }
848
849 if (levelCurrent > levelNext)
850 levelCurrent = levelNext;
851
852 if (!statementFound)
853 levelNext++;
854
855 statementFound = true;
856 } else if (levelCurrent > levelNext) {
857 // doesn't include this line into the folding block
858 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
859 levelCurrent = levelNext;
860 }
861 } else if ((!options.foldOnlyBegin) && (
862 // folding for ELSE and ELSIF block only if foldAtElse is set
863 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
864 options.foldAtElse && !statementFound) && strcmp(s, "elsif") == 0) {
865 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
866 levelCurrent--;
867 levelNext--;
868 } else if ((!options.foldOnlyBegin) && (
869 // folding for ELSE and ELSIF block only if foldAtElse is set
870 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
871 options.foldAtElse && !statementFound) && strcmp(s, "else") == 0) {
872 // prevent also ELSE is on the same line (eg. "ELSE ... END IF;")
873 statementFound = true;
874 if (sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) && sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
875 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
876 levelNext++;
877 } else {
878 // we are in same case "} ELSE {" in C language
879 levelCurrent--;
880 }
881 } else if (strcmp(s, "begin") == 0) {
882 levelNext++;
883 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, false);
884 } else if ((strcmp(s, "end") == 0) ||
885 // SQL Anywhere permits IF ... ELSE ... ENDIF
886 // will only be active if "endif" appears in the
887 // keyword list.
888 (strcmp(s, "endif") == 0)) {
889 endFound = true;
890 levelNext--;
891 if (sqlStates.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine) && !sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine))
892 levelNext--;
893 if (levelNext < SC_FOLDLEVELBASE) {
894 levelNext = SC_FOLDLEVELBASE;
895 isUnfoldingIgnored = true;
896 }
897 } else if ((!options.foldOnlyBegin) &&
898 strcmp(s, "when") == 0 &&
899 !sqlStates.IsIgnoreWhen(sqlStatesCurrentLine) &&
900 !sqlStates.IsIntoExceptionBlock(sqlStatesCurrentLine) && (
901 sqlStates.IsIntoCaseBlock(sqlStatesCurrentLine) ||
902 sqlStates.IsIntoMergeStatement(sqlStatesCurrentLine)
903 )
904 ) {
905 sqlStatesCurrentLine = sqlStates.IntoCondition(sqlStatesCurrentLine, true);
906
907 // 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")
908 // and same way for MERGE statement.
909 if (!statementFound) {
910 if (!sqlStates.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine)) {
911 levelCurrent--;
912 levelNext--;
913 }
914 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, false);
915 }
916 } else if ((!options.foldOnlyBegin) && strcmp(s, "exit") == 0) {
917 sqlStatesCurrentLine = sqlStates.IgnoreWhen(sqlStatesCurrentLine, true);
918 } else if ((!options.foldOnlyBegin) && !sqlStates.IsIntoDeclareBlock(sqlStatesCurrentLine) && strcmp(s, "exception") == 0) {
919 sqlStatesCurrentLine = sqlStates.IntoExceptionBlock(sqlStatesCurrentLine, true);
920 } else if ((!options.foldOnlyBegin) &&
921 (strcmp(s, "declare") == 0 ||
922 strcmp(s, "function") == 0 ||
923 strcmp(s, "procedure") == 0 ||
924 strcmp(s, "package") == 0)) {
925 sqlStatesCurrentLine = sqlStates.IntoDeclareBlock(sqlStatesCurrentLine, true);
926 } else if ((!options.foldOnlyBegin) &&
927 strcmp(s, "merge") == 0) {
928 sqlStatesCurrentLine = sqlStates.IntoMergeStatement(sqlStatesCurrentLine, true);
929 sqlStatesCurrentLine = sqlStates.CaseMergeWithoutWhenFound(sqlStatesCurrentLine, true);
930 levelNext++;
931 statementFound = true;
932 } else if ((!options.foldOnlyBegin) &&
933 strcmp(s, "create") == 0) {
934 sqlStatesCurrentLine = sqlStates.IntoCreateStatement(sqlStatesCurrentLine, true);
935 } else if ((!options.foldOnlyBegin) &&
936 strcmp(s, "view") == 0 &&
937 sqlStates.IsIntoCreateStatement(sqlStatesCurrentLine)) {
938 sqlStatesCurrentLine = sqlStates.IntoCreateViewStatement(sqlStatesCurrentLine, true);
939 } else if ((!options.foldOnlyBegin) &&
940 strcmp(s, "as") == 0 &&
941 sqlStates.IsIntoCreateViewStatement(sqlStatesCurrentLine) &&
942 ! sqlStates.IsIntoCreateViewAsStatement(sqlStatesCurrentLine)) {
943 sqlStatesCurrentLine = sqlStates.IntoCreateViewAsStatement(sqlStatesCurrentLine, true);
944 levelNext++;
945 }
946 }
947 if (atEOL) {
948 int levelUse = levelCurrent;
949 int lev = levelUse | levelNext << 16;
950 if (visibleChars == 0 && options.foldCompact)
951 lev |= SC_FOLDLEVELWHITEFLAG;
952 if (levelUse < levelNext)
953 lev |= SC_FOLDLEVELHEADERFLAG;
954 if (lev != styler.LevelAt(lineCurrent)) {
955 styler.SetLevel(lineCurrent, lev);
956 }
957 lineCurrent++;
958 levelCurrent = levelNext;
959 visibleChars = 0;
960 statementFound = false;
961 if (!options.foldOnlyBegin)
962 sqlStates.Set(lineCurrent, sqlStatesCurrentLine);
963 }
964 if (!isspacechar(ch)) {
965 visibleChars++;
966 }
967 }
968 }
969
970 LexerModule lmSQL(SCLEX_SQL, LexerSQL::LexerFactorySQL, "sql", sqlWordListDesc);
971