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