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