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