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