]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/lexers/LexMySQL.cxx
Compilation fix for wxMSW with wxUSE_MSGBOX_HOOK==0.
[wxWidgets.git] / src / stc / scintilla / lexers / LexMySQL.cxx
1 /**
2 * Scintilla source code edit control
3 * @file LexMySQL.cxx
4 * Lexer for MySQL
5 *
6 * Improved by Mike Lischke <mike.lischke@sun.com>
7 * Adopted from LexSQL.cxx by Anders Karlsson <anders@mysql.com>
8 * Original work by Neil Hodgson <neilh@scintilla.org>
9 * Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
10 * The License.txt file describes the conditions under which this software may be distributed.
11 */
12
13 #include <stdlib.h>
14 #include <string.h>
15 #include <stdio.h>
16 #include <stdarg.h>
17 #include <assert.h>
18 #include <ctype.h>
19
20 #include "ILexer.h"
21 #include "Scintilla.h"
22 #include "SciLexer.h"
23
24 #include "WordList.h"
25 #include "LexAccessor.h"
26 #include "Accessor.h"
27 #include "StyleContext.h"
28 #include "CharacterSet.h"
29 #include "LexerModule.h"
30
31 #ifdef SCI_NAMESPACE
32 using namespace Scintilla;
33 #endif
34
35 static inline bool IsAWordChar(int ch) {
36 return (ch < 0x80) && (isalnum(ch) || ch == '_');
37 }
38
39 static inline bool IsAWordStart(int ch) {
40 return (ch < 0x80) && (isalpha(ch) || ch == '_');
41 }
42
43 static inline bool IsADoxygenChar(int ch) {
44 return (islower(ch) || ch == '$' || ch == '@' ||
45 ch == '\\' || ch == '&' || ch == '<' ||
46 ch == '>' || ch == '#' || ch == '{' ||
47 ch == '}' || ch == '[' || ch == ']');
48 }
49
50 static inline bool IsANumberChar(int ch) {
51 // Not exactly following number definition (several dots are seen as OK, etc.)
52 // but probably enough in most cases.
53 return (ch < 0x80) &&
54 (isdigit(ch) || toupper(ch) == 'E' ||
55 ch == '.' || ch == '-' || ch == '+');
56 }
57
58 //--------------------------------------------------------------------------------------------------
59
60 /**
61 * Check if the current content context represent a keyword and set the context state if so.
62 */
63 static void CheckForKeyword(StyleContext& sc, WordList* keywordlists[])
64 {
65 int length = sc.LengthCurrent() + 1; // +1 for the next char
66 char* s = new char[length];
67 sc.GetCurrentLowered(s, length);
68 if (keywordlists[0]->InList(s))
69 sc.ChangeState(SCE_MYSQL_MAJORKEYWORD);
70 else
71 if (keywordlists[1]->InList(s))
72 sc.ChangeState(SCE_MYSQL_KEYWORD);
73 else
74 if (keywordlists[2]->InList(s))
75 sc.ChangeState(SCE_MYSQL_DATABASEOBJECT);
76 else
77 if (keywordlists[3]->InList(s))
78 sc.ChangeState(SCE_MYSQL_FUNCTION);
79 else
80 if (keywordlists[5]->InList(s))
81 sc.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD);
82 else
83 if (keywordlists[6]->InList(s))
84 sc.ChangeState(SCE_MYSQL_USER1);
85 else
86 if (keywordlists[7]->InList(s))
87 sc.ChangeState(SCE_MYSQL_USER2);
88 else
89 if (keywordlists[8]->InList(s))
90 sc.ChangeState(SCE_MYSQL_USER3);
91 delete [] s;
92 }
93
94 //--------------------------------------------------------------------------------------------------
95
96 static void ColouriseMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
97 Accessor &styler)
98 {
99 StyleContext sc(startPos, length, initStyle, styler);
100
101 for (; sc.More(); sc.Forward())
102 {
103 // Determine if the current state should terminate.
104 switch (sc.state)
105 {
106 case SCE_MYSQL_OPERATOR:
107 sc.SetState(SCE_MYSQL_DEFAULT);
108 break;
109 case SCE_MYSQL_NUMBER:
110 // We stop the number definition on non-numerical non-dot non-eE non-sign char.
111 if (!IsANumberChar(sc.ch))
112 sc.SetState(SCE_MYSQL_DEFAULT);
113 break;
114 case SCE_MYSQL_IDENTIFIER:
115 // Switch from identifier to keyword state and open a new state for the new char.
116 if (!IsAWordChar(sc.ch))
117 {
118 CheckForKeyword(sc, keywordlists);
119
120 // Additional check for function keywords needed.
121 // A function name must be followed by an opening parenthesis.
122 if (sc.state == SCE_MYSQL_FUNCTION && sc.ch != '(')
123 sc.ChangeState(SCE_MYSQL_DEFAULT);
124
125 sc.SetState(SCE_MYSQL_DEFAULT);
126 }
127 break;
128 case SCE_MYSQL_VARIABLE:
129 if (!IsAWordChar(sc.ch))
130 sc.SetState(SCE_MYSQL_DEFAULT);
131 break;
132 case SCE_MYSQL_SYSTEMVARIABLE:
133 if (!IsAWordChar(sc.ch))
134 {
135 int length = sc.LengthCurrent() + 1;
136 char* s = new char[length];
137 sc.GetCurrentLowered(s, length);
138
139 // Check for known system variables here.
140 if (keywordlists[4]->InList(&s[2]))
141 sc.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE);
142 delete [] s;
143
144 sc.SetState(SCE_MYSQL_DEFAULT);
145 }
146 break;
147 case SCE_MYSQL_QUOTEDIDENTIFIER:
148 if (sc.ch == '`')
149 {
150 if (sc.chNext == '`')
151 sc.Forward(); // Ignore it
152 else
153 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
154 }
155 break;
156 case SCE_MYSQL_COMMENT:
157 case SCE_MYSQL_HIDDENCOMMAND:
158 if (sc.Match('*', '/'))
159 {
160 sc.Forward();
161 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
162 }
163 break;
164 case SCE_MYSQL_COMMENTLINE:
165 if (sc.atLineStart)
166 sc.SetState(SCE_MYSQL_DEFAULT);
167 break;
168 case SCE_MYSQL_SQSTRING:
169 if (sc.ch == '\\')
170 sc.Forward(); // Escape sequence
171 else
172 if (sc.ch == '\'')
173 {
174 // End of single quoted string reached?
175 if (sc.chNext == '\'')
176 sc.Forward();
177 else
178 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
179 }
180 break;
181 case SCE_MYSQL_DQSTRING:
182 if (sc.ch == '\\')
183 sc.Forward(); // Escape sequence
184 else
185 if (sc.ch == '\"')
186 {
187 // End of single quoted string reached?
188 if (sc.chNext == '\"')
189 sc.Forward();
190 else
191 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
192 }
193 break;
194 }
195
196 // Determine if a new state should be entered.
197 if (sc.state == SCE_MYSQL_DEFAULT)
198 {
199 switch (sc.ch)
200 {
201 case '@':
202 if (sc.chNext == '@')
203 {
204 sc.SetState(SCE_MYSQL_SYSTEMVARIABLE);
205 sc.Forward(2); // Skip past @@.
206 }
207 else
208 if (IsAWordStart(sc.ch))
209 {
210 sc.SetState(SCE_MYSQL_VARIABLE);
211 sc.Forward(); // Skip past @.
212 }
213 else
214 sc.SetState(SCE_MYSQL_OPERATOR);
215 break;
216 case '`':
217 sc.SetState(SCE_MYSQL_QUOTEDIDENTIFIER);
218 break;
219 case '#':
220 sc.SetState(SCE_MYSQL_COMMENTLINE);
221 break;
222 case '\'':
223 sc.SetState(SCE_MYSQL_SQSTRING);
224 break;
225 case '\"':
226 sc.SetState(SCE_MYSQL_DQSTRING);
227 break;
228 default:
229 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext)))
230 sc.SetState(SCE_MYSQL_NUMBER);
231 else
232 if (IsAWordStart(sc.ch))
233 sc.SetState(SCE_MYSQL_IDENTIFIER);
234 else
235 if (sc.Match('/', '*'))
236 {
237 sc.SetState(SCE_MYSQL_COMMENT);
238
239 // Skip comment introducer and check for hidden command.
240 sc.Forward(2);
241 if (sc.ch == '!')
242 {
243 sc.ChangeState(SCE_MYSQL_HIDDENCOMMAND);
244 sc.Forward();
245 }
246 }
247 else
248 if (sc.Match("--"))
249 {
250 // Special MySQL single line comment.
251 sc.SetState(SCE_MYSQL_COMMENTLINE);
252 sc.Forward(2);
253
254 // Check the third character too. It must be a space or EOL.
255 if (sc.ch != ' ' && sc.ch != '\n' && sc.ch != '\r')
256 sc.ChangeState(SCE_MYSQL_OPERATOR);
257 }
258 else
259 if (isoperator(static_cast<char>(sc.ch)))
260 sc.SetState(SCE_MYSQL_OPERATOR);
261 }
262 }
263 }
264
265 // Do a final check for keywords if we currently have an identifier, to highlight them
266 // also at the end of a line.
267 if (sc.state == SCE_MYSQL_IDENTIFIER)
268 {
269 CheckForKeyword(sc, keywordlists);
270
271 // Additional check for function keywords needed.
272 // A function name must be followed by an opening parenthesis.
273 if (sc.state == SCE_MYSQL_FUNCTION && sc.ch != '(')
274 sc.ChangeState(SCE_MYSQL_DEFAULT);
275 }
276
277 sc.Complete();
278 }
279
280 //--------------------------------------------------------------------------------------------------
281
282 /**
283 * Helper function to determine if we have a foldable comment currently.
284 */
285 static bool IsStreamCommentStyle(int style)
286 {
287 return style == SCE_MYSQL_COMMENT;
288 }
289
290 //--------------------------------------------------------------------------------------------------
291
292 /**
293 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
294 * companion to Match()...
295 */
296 bool MatchIgnoreCase(Accessor &styler, int currentPos, const char *s)
297 {
298 for (int n = 0; *s; n++)
299 {
300 if (*s != tolower(styler.SafeGetCharAt(currentPos + n)))
301 return false;
302 s++;
303 }
304 return true;
305 }
306
307 //--------------------------------------------------------------------------------------------------
308
309 // Store both the current line's fold level and the next lines in the
310 // level store to make it easy to pick up with each increment.
311 static void FoldMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *[], Accessor &styler)
312 {
313 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
314 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
315 bool foldOnlyBegin = styler.GetPropertyInt("fold.sql.only.begin", 0) != 0;
316
317 int visibleChars = 0;
318 int lineCurrent = styler.GetLine(startPos);
319 int levelCurrent = SC_FOLDLEVELBASE;
320 if (lineCurrent > 0)
321 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
322 int levelNext = levelCurrent;
323
324 int styleNext = styler.StyleAt(startPos);
325 int style = initStyle;
326
327 bool endPending = false;
328 bool whenPending = false;
329 bool elseIfPending = false;
330
331 char nextChar = styler.SafeGetCharAt(startPos);
332 for (unsigned int i = startPos; length > 0; i++, length--)
333 {
334 int stylePrev = style;
335 style = styleNext;
336 styleNext = styler.StyleAt(i + 1);
337
338 char currentChar = nextChar;
339 nextChar = styler.SafeGetCharAt(i + 1);
340 bool atEOL = (currentChar == '\r' && nextChar != '\n') || (currentChar == '\n');
341
342 switch (style)
343 {
344 case SCE_MYSQL_COMMENT:
345 if (foldComment)
346 {
347 // Multiline comment style /* .. */.
348 if (IsStreamCommentStyle(style))
349 {
350 // Increase level if we just start a foldable comment.
351 if (!IsStreamCommentStyle(stylePrev))
352 levelNext++;
353 else
354 // If we are in the middle of a foldable comment check if it ends now.
355 // Don't end at the line end, though.
356 if (!IsStreamCommentStyle(styleNext) && !atEOL)
357 levelNext--;
358 }
359 }
360 break;
361 case SCE_MYSQL_COMMENTLINE:
362 if (foldComment)
363 {
364 // Not really a standard, but we add support for single line comments
365 // with special curly braces syntax as foldable comments too.
366 // MySQL needs -- comments to be followed by space or control char
367 if (styler.Match(i, "--"))
368 {
369 char chNext2 = styler.SafeGetCharAt(i + 2);
370 char chNext3 = styler.SafeGetCharAt(i + 3);
371 if (chNext2 == '{' || chNext3 == '{')
372 levelNext++;
373 else
374 if (chNext2 == '}' || chNext3 == '}')
375 levelNext--;
376 }
377 }
378 break;
379 case SCE_MYSQL_HIDDENCOMMAND:
380 if (endPending)
381 {
382 // A conditional command is not a white space so it should end the current block
383 // before opening a new one.
384 endPending = false;
385 levelNext--;
386 if (levelNext < SC_FOLDLEVELBASE)
387 levelNext = SC_FOLDLEVELBASE;
388 }
389 if (style != stylePrev)
390 levelNext++;
391 else
392 if (style != styleNext)
393 {
394 levelNext--;
395 if (levelNext < SC_FOLDLEVELBASE)
396 levelNext = SC_FOLDLEVELBASE;
397 }
398 break;
399 case SCE_MYSQL_OPERATOR:
400 if (endPending)
401 {
402 endPending = false;
403 levelNext--;
404 if (levelNext < SC_FOLDLEVELBASE)
405 levelNext = SC_FOLDLEVELBASE;
406 }
407 if (currentChar == '(')
408 levelNext++;
409 else
410 if (currentChar == ')')
411 {
412 levelNext--;
413 if (levelNext < SC_FOLDLEVELBASE)
414 levelNext = SC_FOLDLEVELBASE;
415 }
416 break;
417 case SCE_MYSQL_MAJORKEYWORD:
418 case SCE_MYSQL_KEYWORD:
419 case SCE_MYSQL_FUNCTION:
420 case SCE_MYSQL_PROCEDUREKEYWORD:
421 // Reserved and other keywords.
422 if (style != stylePrev)
423 {
424 // END decreases the folding level, regardless which keyword follows.
425 bool endFound = MatchIgnoreCase(styler, i, "end");
426 if (endPending)
427 {
428 levelNext--;
429 if (levelNext < SC_FOLDLEVELBASE)
430 levelNext = SC_FOLDLEVELBASE;
431 }
432 else
433 if (!endFound)
434 {
435 if (MatchIgnoreCase(styler, i, "begin"))
436 levelNext++;
437 else
438 {
439 if (!foldOnlyBegin)
440 {
441 bool whileFound = MatchIgnoreCase(styler, i, "while");
442 bool loopFound = MatchIgnoreCase(styler, i, "loop");
443 bool repeatFound = MatchIgnoreCase(styler, i, "repeat");
444 bool caseFound = MatchIgnoreCase(styler, i, "case");
445
446 if (whileFound || loopFound || repeatFound || caseFound)
447 levelNext++;
448 else
449 {
450 // IF alone does not increase the fold level as it is also used in non-block'ed
451 // code like DROP PROCEDURE blah IF EXISTS.
452 // Instead THEN opens the new level (if not part of an ELSEIF or WHEN (case) branch).
453 if (MatchIgnoreCase(styler, i, "then"))
454 {
455 if (!elseIfPending && !whenPending)
456 levelNext++;
457 else
458 {
459 elseIfPending = false;
460 whenPending = false;
461 }
462 }
463 else
464 {
465 // Neither of if/then/while/loop/repeat/case, so check for
466 // sub parts of IF and CASE.
467 if (MatchIgnoreCase(styler, i, "elseif"))
468 elseIfPending = true;
469 if (MatchIgnoreCase(styler, i, "when"))
470 whenPending = true;
471 }
472 }
473 }
474 }
475 }
476
477 // Keep the current end state for the next round.
478 endPending = endFound;
479 }
480 break;
481
482 default:
483 if (!isspace(currentChar) && endPending)
484 {
485 // END followed by a non-whitespace character (not covered by other cases like identifiers)
486 // also should end a folding block. Typical case: END followed by self defined delimiter.
487 levelNext--;
488 if (levelNext < SC_FOLDLEVELBASE)
489 levelNext = SC_FOLDLEVELBASE;
490 }
491 break;
492 }
493
494 if (atEOL)
495 {
496 // Apply the new folding level to this line.
497 // Leave pending states as they are otherwise a line break will de-sync
498 // code folding and valid syntax.
499 int levelUse = levelCurrent;
500 int lev = levelUse | levelNext << 16;
501 if (visibleChars == 0 && foldCompact)
502 lev |= SC_FOLDLEVELWHITEFLAG;
503 if (levelUse < levelNext)
504 lev |= SC_FOLDLEVELHEADERFLAG;
505 if (lev != styler.LevelAt(lineCurrent))
506 styler.SetLevel(lineCurrent, lev);
507
508 lineCurrent++;
509 levelCurrent = levelNext;
510 visibleChars = 0;
511 }
512
513 if (!isspacechar(currentChar))
514 visibleChars++;
515 }
516 }
517
518 //--------------------------------------------------------------------------------------------------
519
520 static const char * const mysqlWordListDesc[] = {
521 "Major Keywords",
522 "Keywords",
523 "Database Objects",
524 "Functions",
525 "System Variables",
526 "Procedure keywords",
527 "User Keywords 1",
528 "User Keywords 2",
529 "User Keywords 3",
530 0
531 };
532
533 LexerModule lmMySQL(SCLEX_MYSQL, ColouriseMySQLDoc, "mysql", FoldMySQLDoc, mysqlWordListDesc);