Minor Scintilla update to 3.2.4
[TortoiseGit.git] / ext / scintilla / lexers / LexMySQL.cxx
blob6b9f7497a661d3635fd8433aafa6dc1587c432ff
1 /**
2 * Scintilla source code edit control
3 * @file LexMySQL.cxx
4 * Lexer for MySQL
6 * Improved by Mike Lischke <mike.lischke@oracle.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.
13 #include <stdlib.h>
14 #include <string.h>
15 #include <stdio.h>
16 #include <stdarg.h>
17 #include <assert.h>
18 #include <ctype.h>
20 #include "ILexer.h"
21 #include "Scintilla.h"
22 #include "SciLexer.h"
24 #include "WordList.h"
25 #include "LexAccessor.h"
26 #include "Accessor.h"
27 #include "StyleContext.h"
28 #include "CharacterSet.h"
29 #include "LexerModule.h"
31 #ifdef SCI_NAMESPACE
32 using namespace Scintilla;
33 #endif
35 static inline bool IsAWordChar(int ch) {
36 return (ch < 0x80) && (isalnum(ch) || ch == '_');
39 static inline bool IsAWordStart(int ch) {
40 return (ch < 0x80) && (isalpha(ch) || ch == '_');
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 == ']');
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 == '+');
58 //--------------------------------------------------------------------------------------------------
60 /**
61 * Check if the current content context represent a keyword and set the context state if so.
63 static void CheckForKeyword(StyleContext& sc, WordList* keywordlists[], int activeState)
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 | activeState);
70 else
71 if (keywordlists[1]->InList(s))
72 sc.ChangeState(SCE_MYSQL_KEYWORD | activeState);
73 else
74 if (keywordlists[2]->InList(s))
75 sc.ChangeState(SCE_MYSQL_DATABASEOBJECT | activeState);
76 else
77 if (keywordlists[3]->InList(s))
78 sc.ChangeState(SCE_MYSQL_FUNCTION | activeState);
79 else
80 if (keywordlists[5]->InList(s))
81 sc.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD | activeState);
82 else
83 if (keywordlists[6]->InList(s))
84 sc.ChangeState(SCE_MYSQL_USER1 | activeState);
85 else
86 if (keywordlists[7]->InList(s))
87 sc.ChangeState(SCE_MYSQL_USER2 | activeState);
88 else
89 if (keywordlists[8]->InList(s))
90 sc.ChangeState(SCE_MYSQL_USER3 | activeState);
91 delete [] s;
94 //--------------------------------------------------------------------------------------------------
96 #define HIDDENCOMMAND_STATE 0x40 // Offset for states within a hidden command.
97 #define MASKACTIVE(style) (style & ~HIDDENCOMMAND_STATE)
99 static void SetDefaultState(StyleContext& sc, int activeState)
101 if (activeState == 0)
102 sc.SetState(SCE_MYSQL_DEFAULT);
103 else
104 sc.SetState(SCE_MYSQL_HIDDENCOMMAND);
107 static void ForwardDefaultState(StyleContext& sc, int activeState)
109 if (activeState == 0)
110 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
111 else
112 sc.ForwardSetState(SCE_MYSQL_HIDDENCOMMAND);
115 static void ColouriseMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
116 Accessor &styler)
118 StyleContext sc(startPos, length, initStyle, styler, 127);
119 int activeState = (initStyle == SCE_MYSQL_HIDDENCOMMAND) ? HIDDENCOMMAND_STATE : initStyle & HIDDENCOMMAND_STATE;
121 for (; sc.More(); sc.Forward())
123 // Determine if the current state should terminate.
124 switch (MASKACTIVE(sc.state))
126 case SCE_MYSQL_OPERATOR:
127 SetDefaultState(sc, activeState);
128 break;
129 case SCE_MYSQL_NUMBER:
130 // We stop the number definition on non-numerical non-dot non-eE non-sign char.
131 if (!IsANumberChar(sc.ch))
132 SetDefaultState(sc, activeState);
133 break;
134 case SCE_MYSQL_IDENTIFIER:
135 // Switch from identifier to keyword state and open a new state for the new char.
136 if (!IsAWordChar(sc.ch))
138 CheckForKeyword(sc, keywordlists, activeState);
140 // Additional check for function keywords needed.
141 // A function name must be followed by an opening parenthesis.
142 if (MASKACTIVE(sc.state) == SCE_MYSQL_FUNCTION && sc.ch != '(')
144 if (activeState > 0)
145 sc.ChangeState(SCE_MYSQL_HIDDENCOMMAND);
146 else
147 sc.ChangeState(SCE_MYSQL_DEFAULT);
150 SetDefaultState(sc, activeState);
152 break;
153 case SCE_MYSQL_VARIABLE:
154 if (!IsAWordChar(sc.ch))
155 SetDefaultState(sc, activeState);
156 break;
157 case SCE_MYSQL_SYSTEMVARIABLE:
158 if (!IsAWordChar(sc.ch))
160 int length = sc.LengthCurrent() + 1;
161 char* s = new char[length];
162 sc.GetCurrentLowered(s, length);
164 // Check for known system variables here.
165 if (keywordlists[4]->InList(&s[2]))
166 sc.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE | activeState);
167 delete [] s;
169 SetDefaultState(sc, activeState);
171 break;
172 case SCE_MYSQL_QUOTEDIDENTIFIER:
173 if (sc.ch == '`')
175 if (sc.chNext == '`')
176 sc.Forward(); // Ignore it
177 else
178 ForwardDefaultState(sc, activeState);
180 break;
181 case SCE_MYSQL_COMMENT:
182 if (sc.Match('*', '/'))
184 sc.Forward();
185 ForwardDefaultState(sc, activeState);
187 break;
188 case SCE_MYSQL_COMMENTLINE:
189 if (sc.atLineStart)
190 SetDefaultState(sc, activeState);
191 break;
192 case SCE_MYSQL_SQSTRING:
193 if (sc.ch == '\\')
194 sc.Forward(); // Escape sequence
195 else
196 if (sc.ch == '\'')
198 // End of single quoted string reached?
199 if (sc.chNext == '\'')
200 sc.Forward();
201 else
202 ForwardDefaultState(sc, activeState);
204 break;
205 case SCE_MYSQL_DQSTRING:
206 if (sc.ch == '\\')
207 sc.Forward(); // Escape sequence
208 else
209 if (sc.ch == '\"')
211 // End of single quoted string reached?
212 if (sc.chNext == '\"')
213 sc.Forward();
214 else
215 ForwardDefaultState(sc, activeState);
217 break;
218 case SCE_MYSQL_PLACEHOLDER:
219 if (sc.Match('}', '>'))
221 sc.Forward();
222 ForwardDefaultState(sc, activeState);
224 break;
227 if (sc.state == SCE_MYSQL_HIDDENCOMMAND && sc.Match('*', '/'))
229 activeState = 0;
230 sc.Forward();
231 ForwardDefaultState(sc, activeState);
234 // Determine if a new state should be entered.
235 if (sc.state == SCE_MYSQL_DEFAULT || sc.state == SCE_MYSQL_HIDDENCOMMAND)
237 switch (sc.ch)
239 case '@':
240 if (sc.chNext == '@')
242 sc.SetState(SCE_MYSQL_SYSTEMVARIABLE | activeState);
243 sc.Forward(2); // Skip past @@.
245 else
246 if (IsAWordStart(sc.ch))
248 sc.SetState(SCE_MYSQL_VARIABLE | activeState);
249 sc.Forward(); // Skip past @.
251 else
252 sc.SetState(SCE_MYSQL_OPERATOR | activeState);
253 break;
254 case '`':
255 sc.SetState(SCE_MYSQL_QUOTEDIDENTIFIER | activeState);
256 break;
257 case '#':
258 sc.SetState(SCE_MYSQL_COMMENTLINE | activeState);
259 break;
260 case '\'':
261 sc.SetState(SCE_MYSQL_SQSTRING | activeState);
262 break;
263 case '\"':
264 sc.SetState(SCE_MYSQL_DQSTRING | activeState);
265 break;
266 default:
267 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext)))
268 sc.SetState(SCE_MYSQL_NUMBER | activeState);
269 else
270 if (IsAWordStart(sc.ch))
271 sc.SetState(SCE_MYSQL_IDENTIFIER | activeState);
272 else
273 if (sc.Match('/', '*'))
275 sc.SetState(SCE_MYSQL_COMMENT | activeState);
277 // Skip comment introducer and check for hidden command.
278 sc.Forward(2);
279 if (sc.ch == '!')
281 activeState = HIDDENCOMMAND_STATE;
282 sc.ChangeState(SCE_MYSQL_HIDDENCOMMAND);
285 else if (sc.Match('<', '{'))
287 sc.SetState(SCE_MYSQL_PLACEHOLDER | activeState);
289 else
290 if (sc.Match("--"))
292 // Special MySQL single line comment.
293 sc.SetState(SCE_MYSQL_COMMENTLINE | activeState);
294 sc.Forward(2);
296 // Check the third character too. It must be a space or EOL.
297 if (sc.ch != ' ' && sc.ch != '\n' && sc.ch != '\r')
298 sc.ChangeState(SCE_MYSQL_OPERATOR | activeState);
300 else
301 if (isoperator(static_cast<char>(sc.ch)))
302 sc.SetState(SCE_MYSQL_OPERATOR | activeState);
307 // Do a final check for keywords if we currently have an identifier, to highlight them
308 // also at the end of a line.
309 if (sc.state == SCE_MYSQL_IDENTIFIER)
311 CheckForKeyword(sc, keywordlists, activeState);
313 // Additional check for function keywords needed.
314 // A function name must be followed by an opening parenthesis.
315 if (sc.state == SCE_MYSQL_FUNCTION && sc.ch != '(')
316 SetDefaultState(sc, activeState);
319 sc.Complete();
322 //--------------------------------------------------------------------------------------------------
325 * Helper function to determine if we have a foldable comment currently.
327 static bool IsStreamCommentStyle(int style)
329 return MASKACTIVE(style) == SCE_MYSQL_COMMENT;
332 //--------------------------------------------------------------------------------------------------
335 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
336 * companion to Match()...
338 bool MatchIgnoreCase(Accessor &styler, int currentPos, const char *s)
340 for (int n = 0; *s; n++)
342 if (*s != tolower(styler.SafeGetCharAt(currentPos + n)))
343 return false;
344 s++;
346 return true;
349 //--------------------------------------------------------------------------------------------------
351 // Store both the current line's fold level and the next lines in the
352 // level store to make it easy to pick up with each increment.
353 static void FoldMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *[], Accessor &styler)
355 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
356 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
357 bool foldOnlyBegin = styler.GetPropertyInt("fold.sql.only.begin", 0) != 0;
359 int visibleChars = 0;
360 int lineCurrent = styler.GetLine(startPos);
361 int levelCurrent = SC_FOLDLEVELBASE;
362 if (lineCurrent > 0)
363 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
364 int levelNext = levelCurrent;
366 int styleNext = styler.StyleAt(startPos);
367 int style = initStyle;
368 int activeState = (style == SCE_MYSQL_HIDDENCOMMAND) ? HIDDENCOMMAND_STATE : style & HIDDENCOMMAND_STATE;
370 bool endPending = false;
371 bool whenPending = false;
372 bool elseIfPending = false;
374 char nextChar = styler.SafeGetCharAt(startPos);
375 for (unsigned int i = startPos; length > 0; i++, length--)
377 int stylePrev = style;
378 int lastActiveState = activeState;
379 style = styleNext;
380 styleNext = styler.StyleAt(i + 1);
381 activeState = (style == SCE_MYSQL_HIDDENCOMMAND) ? HIDDENCOMMAND_STATE : style & HIDDENCOMMAND_STATE;
383 char currentChar = nextChar;
384 nextChar = styler.SafeGetCharAt(i + 1);
385 bool atEOL = (currentChar == '\r' && nextChar != '\n') || (currentChar == '\n');
387 switch (MASKACTIVE(style))
389 case SCE_MYSQL_COMMENT:
390 if (foldComment)
392 // Multiline comment style /* .. */ just started or is still in progress.
393 if (IsStreamCommentStyle(style) && !IsStreamCommentStyle(stylePrev))
394 levelNext++;
396 break;
397 case SCE_MYSQL_COMMENTLINE:
398 if (foldComment)
400 // Not really a standard, but we add support for single line comments
401 // with special curly braces syntax as foldable comments too.
402 // MySQL needs -- comments to be followed by space or control char
403 if (styler.Match(i, "--"))
405 char chNext2 = styler.SafeGetCharAt(i + 2);
406 char chNext3 = styler.SafeGetCharAt(i + 3);
407 if (chNext2 == '{' || chNext3 == '{')
408 levelNext++;
409 else
410 if (chNext2 == '}' || chNext3 == '}')
411 levelNext--;
414 break;
415 case SCE_MYSQL_HIDDENCOMMAND:
417 if (endPending)
419 // A conditional command is not a white space so it should end the current block
420 // before opening a new one.
421 endPending = false;
422 levelNext--;
423 if (levelNext < SC_FOLDLEVELBASE)
424 levelNext = SC_FOLDLEVELBASE;
427 if (activeState != lastActiveState)
428 levelNext++;
429 break;
430 case SCE_MYSQL_OPERATOR:
431 if (endPending)
433 endPending = false;
434 levelNext--;
435 if (levelNext < SC_FOLDLEVELBASE)
436 levelNext = SC_FOLDLEVELBASE;
438 if (currentChar == '(')
439 levelNext++;
440 else
441 if (currentChar == ')')
443 levelNext--;
444 if (levelNext < SC_FOLDLEVELBASE)
445 levelNext = SC_FOLDLEVELBASE;
447 break;
448 case SCE_MYSQL_MAJORKEYWORD:
449 case SCE_MYSQL_KEYWORD:
450 case SCE_MYSQL_FUNCTION:
451 case SCE_MYSQL_PROCEDUREKEYWORD:
452 // Reserved and other keywords.
453 if (style != stylePrev)
455 // END decreases the folding level, regardless which keyword follows.
456 bool endFound = MatchIgnoreCase(styler, i, "end");
457 if (endPending)
459 levelNext--;
460 if (levelNext < SC_FOLDLEVELBASE)
461 levelNext = SC_FOLDLEVELBASE;
463 else
464 if (!endFound)
466 if (MatchIgnoreCase(styler, i, "begin"))
467 levelNext++;
468 else
470 if (!foldOnlyBegin)
472 bool whileFound = MatchIgnoreCase(styler, i, "while");
473 bool loopFound = MatchIgnoreCase(styler, i, "loop");
474 bool repeatFound = MatchIgnoreCase(styler, i, "repeat");
475 bool caseFound = MatchIgnoreCase(styler, i, "case");
477 if (whileFound || loopFound || repeatFound || caseFound)
478 levelNext++;
479 else
481 // IF alone does not increase the fold level as it is also used in non-block'ed
482 // code like DROP PROCEDURE blah IF EXISTS.
483 // Instead THEN opens the new level (if not part of an ELSEIF or WHEN (case) branch).
484 if (MatchIgnoreCase(styler, i, "then"))
486 if (!elseIfPending && !whenPending)
487 levelNext++;
488 else
490 elseIfPending = false;
491 whenPending = false;
494 else
496 // Neither of if/then/while/loop/repeat/case, so check for
497 // sub parts of IF and CASE.
498 if (MatchIgnoreCase(styler, i, "elseif"))
499 elseIfPending = true;
500 if (MatchIgnoreCase(styler, i, "when"))
501 whenPending = true;
508 // Keep the current end state for the next round.
509 endPending = endFound;
511 break;
513 default:
514 if (!isspacechar(currentChar) && endPending)
516 // END followed by a non-whitespace character (not covered by other cases like identifiers)
517 // also should end a folding block. Typical case: END followed by self defined delimiter.
518 levelNext--;
519 if (levelNext < SC_FOLDLEVELBASE)
520 levelNext = SC_FOLDLEVELBASE;
522 break;
525 // Go up one level if we just ended a multi line comment.
526 if (IsStreamCommentStyle(stylePrev) && !IsStreamCommentStyle(style))
528 levelNext--;
529 if (levelNext < SC_FOLDLEVELBASE)
530 levelNext = SC_FOLDLEVELBASE;
533 if (activeState == 0 && lastActiveState != 0)
535 // Decrease fold level when we left a hidden command.
536 levelNext--;
537 if (levelNext < SC_FOLDLEVELBASE)
538 levelNext = SC_FOLDLEVELBASE;
541 if (atEOL)
543 // Apply the new folding level to this line.
544 // Leave pending states as they are otherwise a line break will de-sync
545 // code folding and valid syntax.
546 int levelUse = levelCurrent;
547 int lev = levelUse | levelNext << 16;
548 if (visibleChars == 0 && foldCompact)
549 lev |= SC_FOLDLEVELWHITEFLAG;
550 if (levelUse < levelNext)
551 lev |= SC_FOLDLEVELHEADERFLAG;
552 if (lev != styler.LevelAt(lineCurrent))
553 styler.SetLevel(lineCurrent, lev);
555 lineCurrent++;
556 levelCurrent = levelNext;
557 visibleChars = 0;
560 if (!isspacechar(currentChar))
561 visibleChars++;
565 //--------------------------------------------------------------------------------------------------
567 static const char * const mysqlWordListDesc[] = {
568 "Major Keywords",
569 "Keywords",
570 "Database Objects",
571 "Functions",
572 "System Variables",
573 "Procedure keywords",
574 "User Keywords 1",
575 "User Keywords 2",
576 "User Keywords 3",
580 LexerModule lmMySQL(SCLEX_MYSQL, ColouriseMySQLDoc, "mysql", FoldMySQLDoc, mysqlWordListDesc, 7);