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