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