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