1 // Scintilla source code edit control
2 /** @file LexSQL.cxx
3  ** Lexer for SQL.
4  **/
5 // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdarg.h>
13 
14 #include "Platform.h"
15 
16 #include "PropSet.h"
17 #include "Accessor.h"
18 #include "KeyWords.h"
19 #include "Scintilla.h"
20 #include "SciLexer.h"
21 
22 
IsASpace(unsigned int ch)23 static inline bool IsASpace(unsigned int ch) {
24     return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
25 }
26 
MatchIgnoreCaseSubstring(const char * s,Accessor & styler,unsigned int startPos)27 static bool MatchIgnoreCaseSubstring(const char *s, Accessor &styler, unsigned int startPos) {
28 	char ch = styler.SafeGetCharAt(startPos);
29 	bool isSubword = false;
30 	if (tolower(ch) != *s)
31 		return false;
32 	s++;
33 	if (*s == '~')
34 	{
35 		isSubword = true;
36 		s++;
37 	}
38 	int n;
39 	for (n = 1; *s; n++) {
40 		if (*s == '~')
41 		{
42 			isSubword = true;
43 			s++;
44 		}
45 		if (isSubword && IsASpace(styler.SafeGetCharAt(startPos + n)))
46 			return true;
47 		if (*s != tolower((styler.SafeGetCharAt(startPos + n))))
48 			return false;
49 		s++;
50 	}
51 	return (IsASpace(styler.SafeGetCharAt(startPos + n))
52 		|| styler.SafeGetCharAt(startPos + n) == ';');
53 }
54 
getCurrent(unsigned int start,unsigned int end,Accessor & styler,char * s,unsigned int len)55 static void getCurrent(unsigned int start,
56 		unsigned int end,
57 		Accessor &styler,
58 		char *s,
59 		unsigned int len) {
60 	for (unsigned int i = 0; i < end - start + 1 && i < len; i++) {
61 		s[i] = static_cast<char>(tolower(styler[start + i]));
62 		s[i + 1] = '\0';
63 	}
64 }
65 
classifyWordSQL(unsigned int start,unsigned int end,WordList * keywordlists[],Accessor & styler)66 static void classifyWordSQL(unsigned int start, unsigned int end, WordList *keywordlists[], Accessor &styler) {
67 	char s[100];
68 	bool wordIsNumber = isdigit(styler[start]) || (styler[start] == '.');
69 	for (unsigned int i = 0; i < end - start + 1 && i < 80; i++) {
70 		s[i] = static_cast<char>(tolower(styler[start + i]));
71 		s[i + 1] = '\0';
72 	}
73 
74 	WordList &keywords1  = *keywordlists[0];
75 	WordList &keywords2  = *keywordlists[1];
76 //	WordList &kw_pldoc   = *keywordlists[2];
77 	WordList &kw_sqlplus = *keywordlists[3];
78 	WordList &kw_user1   = *keywordlists[4];
79 	WordList &kw_user2   = *keywordlists[5];
80 	WordList &kw_user3   = *keywordlists[6];
81 	WordList &kw_user4   = *keywordlists[7];
82 
83 	char chAttr = SCE_SQL_IDENTIFIER;
84 	if (wordIsNumber)
85 		chAttr = SCE_SQL_NUMBER;
86 	else if (keywords1.InList(s))
87 			chAttr = SCE_SQL_WORD;
88 	else if (keywords2.InList(s))
89 			chAttr = SCE_SQL_WORD2;
90 	else if (kw_sqlplus.InListAbbreviated(s, '~'))
91 		chAttr = SCE_SQL_SQLPLUS;
92 	else if (kw_user1.InList(s))
93 		chAttr = SCE_SQL_USER1;
94 	else if (kw_user2.InList(s))
95 		chAttr = SCE_SQL_USER2;
96 	else if (kw_user3.InList(s))
97 		chAttr = SCE_SQL_USER3;
98 	else if (kw_user4.InList(s))
99 		chAttr = SCE_SQL_USER4;
100 
101 	styler.ColourTo(end, chAttr);
102 }
103 
ColouriseSQLDoc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)104 static void ColouriseSQLDoc(unsigned int startPos, int length,
105                             int initStyle, WordList *keywordlists[], Accessor &styler) {
106 
107 	WordList &kw_pldoc = *keywordlists[2];
108 	styler.StartAt(startPos);
109 
110 	bool fold = styler.GetPropertyInt("fold") != 0;
111 	bool sqlBackslashEscapes = styler.GetPropertyInt("sql.backslash.escapes", 0) != 0;
112 	bool sqlBackticksIdentifier = styler.GetPropertyInt("sql.backticks.identifier", 0) != 0;
113 	int lineCurrent = styler.GetLine(startPos);
114 	int spaceFlags = 0;
115 
116 	int state = initStyle;
117 	char chPrev = ' ';
118 	char chNext = styler[startPos];
119 	styler.StartSegment(startPos);
120 	unsigned int lengthDoc = startPos + length;
121 	for (unsigned int i = startPos; i < lengthDoc; i++) {
122 		char ch = chNext;
123 		chNext = styler.SafeGetCharAt(i + 1);
124 
125 		if ((ch == '\r' && chNext != '\n') || (ch == '\n')) {
126 			int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags);
127 			int lev = indentCurrent;
128 			if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
129 				// Only non whitespace lines can be headers
130 				int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags);
131 				if (indentCurrent < (indentNext & ~SC_FOLDLEVELWHITEFLAG)) {
132 					lev |= SC_FOLDLEVELHEADERFLAG;
133 				}
134 			}
135 			if (fold) {
136 				styler.SetLevel(lineCurrent, lev);
137 			}
138 		}
139 
140 		if (styler.IsLeadByte(ch)) {
141 			chNext = styler.SafeGetCharAt(i + 2);
142 			chPrev = ' ';
143 			i += 1;
144 			continue;
145 		}
146 
147 		if (state == SCE_SQL_DEFAULT) {
148 			if (MatchIgnoreCaseSubstring("rem~ark", styler, i)) {
149 				styler.ColourTo(i - 1, state);
150 				state = SCE_SQL_SQLPLUS_COMMENT;
151 			} else if (MatchIgnoreCaseSubstring("pro~mpt", styler, i)) {
152 				styler.ColourTo(i - 1, state);
153 				state = SCE_SQL_SQLPLUS_PROMPT;
154 			} else if (iswordstart(ch)) {
155 				styler.ColourTo(i - 1, state);
156 				state = SCE_SQL_WORD;
157 			} else if (ch == '/' && chNext == '*') {
158 				styler.ColourTo(i - 1, state);
159 				if ((styler.SafeGetCharAt(i + 2)) == '*') {	// Support of Doxygen doc. style
160 					state = SCE_SQL_COMMENTDOC;
161 				} else {
162 					state = SCE_SQL_COMMENT;
163 				}
164 			} else if (ch == '-' && chNext == '-') {
165 				styler.ColourTo(i - 1, state);
166 				state = SCE_SQL_COMMENTLINE;
167 			} else if (ch == '#') {
168 				styler.ColourTo(i - 1, state);
169 				state = SCE_SQL_COMMENTLINEDOC;
170 			} else if (ch == '\'') {
171 				styler.ColourTo(i - 1, state);
172 				state = SCE_SQL_CHARACTER;
173 			} else if (ch == '"') {
174 				styler.ColourTo(i - 1, state);
175 				state = SCE_SQL_STRING;
176 			} else if (ch == 0x60 && sqlBackticksIdentifier) {
177 				styler.ColourTo(i - 1, state);
178 				state = SCE_SQL_BACKTICKS_IDENTIFIER;
179 			} else if (isoperator(ch)) {
180 				styler.ColourTo(i - 1, state);
181 				styler.ColourTo(i, SCE_SQL_OPERATOR);
182 			}
183 		} else if (state == SCE_SQL_WORD) {
184 			if (!iswordchar(ch)) {
185 				classifyWordSQL(styler.GetStartSegment(), i - 1, keywordlists, styler);
186 				state = SCE_SQL_DEFAULT;
187 				if (ch == '/' && chNext == '*') {
188 					if ((styler.SafeGetCharAt(i + 2)) == '*') {	// Support of Doxygen doc. style
189 						state = SCE_SQL_COMMENTDOC;
190 					} else {
191 						state = SCE_SQL_COMMENT;
192 					}
193 				} else if (ch == '-' && chNext == '-') {
194 					state = SCE_SQL_COMMENTLINE;
195 				} else if (ch == '#') {
196 					state = SCE_SQL_COMMENTLINEDOC;
197 				} else if (ch == '\'') {
198 					state = SCE_SQL_CHARACTER;
199 				} else if (ch == '"') {
200 					state = SCE_SQL_STRING;
201 				} else if (ch == 0x60 && sqlBackticksIdentifier) {
202 					state = SCE_SQL_BACKTICKS_IDENTIFIER;
203 				} else if (isoperator(ch)) {
204 					styler.ColourTo(i, SCE_SQL_OPERATOR);
205 				}
206 			}
207 		} else {
208 			if (state == SCE_SQL_COMMENT) {
209 				if (ch == '/' && chPrev == '*') {
210 					if (((i > (styler.GetStartSegment() + 2)) || ((initStyle == SCE_C_COMMENT) &&
211 					    (styler.GetStartSegment() == startPos)))) {
212 						styler.ColourTo(i, state);
213 						state = SCE_SQL_DEFAULT;
214 					}
215 				}
216 			} else if (state == SCE_SQL_COMMENTDOC) {
217 				if (ch == '/' && chPrev == '*') {
218 					if (((i > (styler.GetStartSegment() + 2)) || ((initStyle == SCE_SQL_COMMENTDOC) &&
219 					    (styler.GetStartSegment() == startPos)))) {
220 						styler.ColourTo(i, state);
221 						state = SCE_SQL_DEFAULT;
222 					}
223 				} else if (ch == '@') {
224 					// Verify that we have the conditions to mark a comment-doc-keyword
225 					if ((IsASpace(chPrev) || chPrev == '*') && (!IsASpace(chNext))) {
226 						styler.ColourTo(i - 1, state);
227 						state = SCE_SQL_COMMENTDOCKEYWORD;
228 					}
229 				}
230 			} else if (state == SCE_SQL_COMMENTLINE || state == SCE_SQL_COMMENTLINEDOC || state == SCE_SQL_SQLPLUS_COMMENT) {
231 				if (ch == '\r' || ch == '\n') {
232 					styler.ColourTo(i - 1, state);
233 					state = SCE_SQL_DEFAULT;
234 				}
235 			} else if (state == SCE_SQL_COMMENTDOCKEYWORD) {
236 				if (ch == '/' && chPrev == '*') {
237 					styler.ColourTo(i - 1, SCE_SQL_COMMENTDOCKEYWORDERROR);
238 					state = SCE_SQL_DEFAULT;
239 				} else if (!iswordchar(ch)) {
240 					char s[100];
241 					getCurrent(styler.GetStartSegment(), i - 1, styler, s, 30);
242 					if (!kw_pldoc.InList(s + 1)) {
243 						state = SCE_SQL_COMMENTDOCKEYWORDERROR;
244 					}
245 					styler.ColourTo(i - 1, state);
246 					state = SCE_SQL_COMMENTDOC;
247 				}
248 			} else if (state == SCE_SQL_SQLPLUS_PROMPT) {
249 				if (ch == '\r' || ch == '\n') {
250 					styler.ColourTo(i - 1, state);
251 					state = SCE_SQL_DEFAULT;
252 				}
253 			} else if (state == SCE_SQL_CHARACTER) {
254 				if (sqlBackslashEscapes && ch == '\\') {
255 					i++;
256 					ch = chNext;
257 					chNext = styler.SafeGetCharAt(i + 1);
258 				} else if (ch == '\'') {
259 					if (chNext == '\'') {
260 						i++;
261 					} else {
262 						styler.ColourTo(i, state);
263 						state = SCE_SQL_DEFAULT;
264 						i++;
265 					}
266 					ch = chNext;
267 					chNext = styler.SafeGetCharAt(i + 1);
268 				}
269 			} else if (state == SCE_SQL_STRING) {
270 				if (ch == '"') {
271 					if (chNext == '"') {
272 						i++;
273 					} else {
274 						styler.ColourTo(i, state);
275 						state = SCE_SQL_DEFAULT;
276 						i++;
277 					}
278 					ch = chNext;
279 					chNext = styler.SafeGetCharAt(i + 1);
280 				}
281 			} else if (state == SCE_SQL_BACKTICKS_IDENTIFIER) {
282 				if (ch == 0x60) {
283 					if (chNext == 0x60) {
284 						i++;
285 					} else {
286 						styler.ColourTo(i, state);
287 						state = SCE_SQL_DEFAULT;
288 						i++;
289 					}
290 					ch = chNext;
291 					chNext = styler.SafeGetCharAt(i + 1);
292 				}
293 			}
294 			if (state == SCE_SQL_DEFAULT) {    // One of the above succeeded
295 				if (ch == '/' && chNext == '*') {
296 					if ((styler.SafeGetCharAt(i + 2)) == '*') {	// Support of Doxygen doc. style
297 						state = SCE_SQL_COMMENTDOC;
298 					} else {
299 						state = SCE_SQL_COMMENT;
300 					}
301 				} else if (ch == '-' && chNext == '-') {
302 					state = SCE_SQL_COMMENTLINE;
303 				} else if (ch == '#') {
304 					state = SCE_SQL_COMMENTLINEDOC;
305 				} else if (MatchIgnoreCaseSubstring("rem~ark", styler, i)) {
306 					state = SCE_SQL_SQLPLUS_COMMENT;
307 				} else if (MatchIgnoreCaseSubstring("pro~mpt", styler, i)) {
308 					state = SCE_SQL_SQLPLUS_PROMPT;
309 				} else if (ch == '\'') {
310 					state = SCE_SQL_CHARACTER;
311 				} else if (ch == '"') {
312 					state = SCE_SQL_STRING;
313 				} else if (ch == 0x60 && sqlBackticksIdentifier) {
314 					state = SCE_SQL_BACKTICKS_IDENTIFIER;
315 				} else if (iswordstart(ch)) {
316 					state = SCE_SQL_WORD;
317 				} else if (isoperator(ch)) {
318 					styler.ColourTo(i, SCE_SQL_OPERATOR);
319 				}
320 			}
321 		}
322 		chPrev = ch;
323 	}
324 	styler.ColourTo(lengthDoc - 1, state);
325 }
326 
IsStreamCommentStyle(int style)327 static bool IsStreamCommentStyle(int style) {
328 	return style == SCE_SQL_COMMENT ||
329 	       style == SCE_SQL_COMMENTDOC ||
330 	       style == SCE_SQL_COMMENTDOCKEYWORD ||
331 	       style == SCE_SQL_COMMENTDOCKEYWORDERROR;
332 }
333 
334 // Store both the current line's fold level and the next lines in the
335 // level store to make it easy to pick up with each increment
336 // and to make it possible to fiddle the current level for "} else {".
FoldSQLDoc(unsigned int startPos,int length,int initStyle,WordList * [],Accessor & styler)337 static void FoldSQLDoc(unsigned int startPos, int length, int initStyle,
338                        WordList *[], Accessor &styler) {
339 	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
340 	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
341 	unsigned int endPos = startPos + length;
342 	int visibleChars = 0;
343 	int lineCurrent = styler.GetLine(startPos);
344 	int levelCurrent = SC_FOLDLEVELBASE;
345 	if (lineCurrent > 0)
346 		levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
347 	int levelNext = levelCurrent;
348 	char chNext = styler[startPos];
349 	int styleNext = styler.StyleAt(startPos);
350 	int style = initStyle;
351 	bool endFound = false;
352 	for (unsigned int i = startPos; i < endPos; i++) {
353 		char ch = chNext;
354 		chNext = styler.SafeGetCharAt(i + 1);
355 		int stylePrev = style;
356 		style = styleNext;
357 		styleNext = styler.StyleAt(i + 1);
358 		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
359 		if (foldComment && IsStreamCommentStyle(style)) {
360 			if (!IsStreamCommentStyle(stylePrev)) {
361 				levelNext++;
362 			} else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
363 				// Comments don't end at end of line and the next character may be unstyled.
364 				levelNext--;
365 			}
366 		}
367 		if (foldComment && (style == SCE_SQL_COMMENTLINE)) {
368 			if ((ch == '-') && (chNext == '-')) {
369 				char chNext2 = styler.SafeGetCharAt(i + 2);
370 				if (chNext2 == '{') {
371 					levelNext++;
372 				} else if (chNext2 == '}') {
373 					levelNext--;
374 				}
375 			}
376 		}
377 		if (style == SCE_SQL_WORD) {
378 			if (MatchIgnoreCaseSubstring("elsif", styler, i)) {
379 				// ignore elsif
380 				i += 4;
381 			} else if (MatchIgnoreCaseSubstring("if", styler, i)
382 				|| MatchIgnoreCaseSubstring("loop", styler, i)){
383 					if (endFound){
384 						// ignore
385 						endFound = false;
386 					} else {
387 						levelNext++;
388 					}
389 			} else if (MatchIgnoreCaseSubstring("begin", styler, i)){
390 				levelNext++;
391 			} else if (MatchIgnoreCaseSubstring("end", styler, i)) {
392 				endFound = true;
393 				levelNext--;
394 				if (levelNext < SC_FOLDLEVELBASE)
395 					levelNext = SC_FOLDLEVELBASE;
396 			}
397 		}
398 		if (atEOL) {
399 			int levelUse = levelCurrent;
400 			int lev = levelUse | levelNext << 16;
401 			if (visibleChars == 0 && foldCompact)
402 				lev |= SC_FOLDLEVELWHITEFLAG;
403 			if (levelUse < levelNext)
404 				lev |= SC_FOLDLEVELHEADERFLAG;
405 			if (lev != styler.LevelAt(lineCurrent)) {
406 				styler.SetLevel(lineCurrent, lev);
407 			}
408 			lineCurrent++;
409 			levelCurrent = levelNext;
410 			visibleChars = 0;
411 			endFound = false;
412 		}
413 		if (!isspacechar(ch))
414 			visibleChars++;
415 	}
416 }
417 
418 static const char * const sqlWordListDesc[] = {
419 	"Keywords",
420 	"Database Objects",
421 	"PLDoc",
422 	"SQL*Plus",
423 	"User Keywords 1",
424 	"User Keywords 2",
425 	"User Keywords 3",
426 	"User Keywords 4",
427 	0
428 };
429 
430 LexerModule lmSQL(SCLEX_SQL, ColouriseSQLDoc, "sql", FoldSQLDoc, sqlWordListDesc);
431