Update Scintilla to version 3.4.4
[TortoiseGit.git] / ext / scintilla / lexers / LexMySQL.cxx
blob9c31e1092944f174a525639e1c500d07fcca28ec
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 IsANumberChar(int ch) {
44 // Not exactly following number definition (several dots are seen as OK, etc.)
45 // but probably enough in most cases.
46 return (ch < 0x80) &&
47 (isdigit(ch) || toupper(ch) == 'E' ||
48 ch == '.' || ch == '-' || ch == '+');
51 //--------------------------------------------------------------------------------------------------
53 /**
54 * Check if the current content context represent a keyword and set the context state if so.
56 static void CheckForKeyword(StyleContext& sc, WordList* keywordlists[], int activeState)
58 int length = sc.LengthCurrent() + 1; // +1 for the next char
59 char* s = new char[length];
60 sc.GetCurrentLowered(s, length);
61 if (keywordlists[0]->InList(s))
62 sc.ChangeState(SCE_MYSQL_MAJORKEYWORD | activeState);
63 else
64 if (keywordlists[1]->InList(s))
65 sc.ChangeState(SCE_MYSQL_KEYWORD | activeState);
66 else
67 if (keywordlists[2]->InList(s))
68 sc.ChangeState(SCE_MYSQL_DATABASEOBJECT | activeState);
69 else
70 if (keywordlists[3]->InList(s))
71 sc.ChangeState(SCE_MYSQL_FUNCTION | activeState);
72 else
73 if (keywordlists[5]->InList(s))
74 sc.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD | activeState);
75 else
76 if (keywordlists[6]->InList(s))
77 sc.ChangeState(SCE_MYSQL_USER1 | activeState);
78 else
79 if (keywordlists[7]->InList(s))
80 sc.ChangeState(SCE_MYSQL_USER2 | activeState);
81 else
82 if (keywordlists[8]->InList(s))
83 sc.ChangeState(SCE_MYSQL_USER3 | activeState);
84 delete [] s;
87 //--------------------------------------------------------------------------------------------------
89 #define HIDDENCOMMAND_STATE 0x40 // Offset for states within a hidden command.
90 #define MASKACTIVE(style) (style & ~HIDDENCOMMAND_STATE)
92 static void SetDefaultState(StyleContext& sc, int activeState)
94 if (activeState == 0)
95 sc.SetState(SCE_MYSQL_DEFAULT);
96 else
97 sc.SetState(SCE_MYSQL_HIDDENCOMMAND);
100 static void ForwardDefaultState(StyleContext& sc, int activeState)
102 if (activeState == 0)
103 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
104 else
105 sc.ForwardSetState(SCE_MYSQL_HIDDENCOMMAND);
108 static void ColouriseMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
109 Accessor &styler)
111 StyleContext sc(startPos, length, initStyle, styler, 127);
112 int activeState = (initStyle == SCE_MYSQL_HIDDENCOMMAND) ? HIDDENCOMMAND_STATE : initStyle & HIDDENCOMMAND_STATE;
114 for (; sc.More(); sc.Forward())
116 // Determine if the current state should terminate.
117 switch (MASKACTIVE(sc.state))
119 case SCE_MYSQL_OPERATOR:
120 SetDefaultState(sc, activeState);
121 break;
122 case SCE_MYSQL_NUMBER:
123 // We stop the number definition on non-numerical non-dot non-eE non-sign char.
124 if (!IsANumberChar(sc.ch))
125 SetDefaultState(sc, activeState);
126 break;
127 case SCE_MYSQL_IDENTIFIER:
128 // Switch from identifier to keyword state and open a new state for the new char.
129 if (!IsAWordChar(sc.ch))
131 CheckForKeyword(sc, keywordlists, activeState);
133 // Additional check for function keywords needed.
134 // A function name must be followed by an opening parenthesis.
135 if (MASKACTIVE(sc.state) == SCE_MYSQL_FUNCTION && sc.ch != '(')
137 if (activeState > 0)
138 sc.ChangeState(SCE_MYSQL_HIDDENCOMMAND);
139 else
140 sc.ChangeState(SCE_MYSQL_DEFAULT);
143 SetDefaultState(sc, activeState);
145 break;
146 case SCE_MYSQL_VARIABLE:
147 if (!IsAWordChar(sc.ch))
148 SetDefaultState(sc, activeState);
149 break;
150 case SCE_MYSQL_SYSTEMVARIABLE:
151 if (!IsAWordChar(sc.ch))
153 int length = sc.LengthCurrent() + 1;
154 char* s = new char[length];
155 sc.GetCurrentLowered(s, length);
157 // Check for known system variables here.
158 if (keywordlists[4]->InList(&s[2]))
159 sc.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE | activeState);
160 delete [] s;
162 SetDefaultState(sc, activeState);
164 break;
165 case SCE_MYSQL_QUOTEDIDENTIFIER:
166 if (sc.ch == '`')
168 if (sc.chNext == '`')
169 sc.Forward(); // Ignore it
170 else
171 ForwardDefaultState(sc, activeState);
173 break;
174 case SCE_MYSQL_COMMENT:
175 if (sc.Match('*', '/'))
177 sc.Forward();
178 ForwardDefaultState(sc, activeState);
180 break;
181 case SCE_MYSQL_COMMENTLINE:
182 if (sc.atLineStart)
183 SetDefaultState(sc, activeState);
184 break;
185 case SCE_MYSQL_SQSTRING:
186 if (sc.ch == '\\')
187 sc.Forward(); // Escape sequence
188 else
189 if (sc.ch == '\'')
191 // End of single quoted string reached?
192 if (sc.chNext == '\'')
193 sc.Forward();
194 else
195 ForwardDefaultState(sc, activeState);
197 break;
198 case SCE_MYSQL_DQSTRING:
199 if (sc.ch == '\\')
200 sc.Forward(); // Escape sequence
201 else
202 if (sc.ch == '\"')
204 // End of single quoted string reached?
205 if (sc.chNext == '\"')
206 sc.Forward();
207 else
208 ForwardDefaultState(sc, activeState);
210 break;
211 case SCE_MYSQL_PLACEHOLDER:
212 if (sc.Match('}', '>'))
214 sc.Forward();
215 ForwardDefaultState(sc, activeState);
217 break;
220 if (sc.state == SCE_MYSQL_HIDDENCOMMAND && sc.Match('*', '/'))
222 activeState = 0;
223 sc.Forward();
224 ForwardDefaultState(sc, activeState);
227 // Determine if a new state should be entered.
228 if (sc.state == SCE_MYSQL_DEFAULT || sc.state == SCE_MYSQL_HIDDENCOMMAND)
230 switch (sc.ch)
232 case '@':
233 if (sc.chNext == '@')
235 sc.SetState(SCE_MYSQL_SYSTEMVARIABLE | activeState);
236 sc.Forward(2); // Skip past @@.
238 else
239 if (IsAWordStart(sc.ch))
241 sc.SetState(SCE_MYSQL_VARIABLE | activeState);
242 sc.Forward(); // Skip past @.
244 else
245 sc.SetState(SCE_MYSQL_OPERATOR | activeState);
246 break;
247 case '`':
248 sc.SetState(SCE_MYSQL_QUOTEDIDENTIFIER | activeState);
249 break;
250 case '#':
251 sc.SetState(SCE_MYSQL_COMMENTLINE | activeState);
252 break;
253 case '\'':
254 sc.SetState(SCE_MYSQL_SQSTRING | activeState);
255 break;
256 case '\"':
257 sc.SetState(SCE_MYSQL_DQSTRING | activeState);
258 break;
259 default:
260 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext)))
261 sc.SetState(SCE_MYSQL_NUMBER | activeState);
262 else
263 if (IsAWordStart(sc.ch))
264 sc.SetState(SCE_MYSQL_IDENTIFIER | activeState);
265 else
266 if (sc.Match('/', '*'))
268 sc.SetState(SCE_MYSQL_COMMENT | activeState);
270 // Skip comment introducer and check for hidden command.
271 sc.Forward(2);
272 if (sc.ch == '!')
274 activeState = HIDDENCOMMAND_STATE;
275 sc.ChangeState(SCE_MYSQL_HIDDENCOMMAND);
278 else if (sc.Match('<', '{'))
280 sc.SetState(SCE_MYSQL_PLACEHOLDER | activeState);
282 else
283 if (sc.Match("--"))
285 // Special MySQL single line comment.
286 sc.SetState(SCE_MYSQL_COMMENTLINE | activeState);
287 sc.Forward(2);
289 // Check the third character too. It must be a space or EOL.
290 if (sc.ch != ' ' && sc.ch != '\n' && sc.ch != '\r')
291 sc.ChangeState(SCE_MYSQL_OPERATOR | activeState);
293 else
294 if (isoperator(static_cast<char>(sc.ch)))
295 sc.SetState(SCE_MYSQL_OPERATOR | activeState);
300 // Do a final check for keywords if we currently have an identifier, to highlight them
301 // also at the end of a line.
302 if (sc.state == SCE_MYSQL_IDENTIFIER)
304 CheckForKeyword(sc, keywordlists, activeState);
306 // Additional check for function keywords needed.
307 // A function name must be followed by an opening parenthesis.
308 if (sc.state == SCE_MYSQL_FUNCTION && sc.ch != '(')
309 SetDefaultState(sc, activeState);
312 sc.Complete();
315 //--------------------------------------------------------------------------------------------------
318 * Helper function to determine if we have a foldable comment currently.
320 static bool IsStreamCommentStyle(int style)
322 return MASKACTIVE(style) == SCE_MYSQL_COMMENT;
325 //--------------------------------------------------------------------------------------------------
328 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
329 * companion to Match()...
331 bool MatchIgnoreCase(Accessor &styler, int currentPos, const char *s)
333 for (int n = 0; *s; n++)
335 if (*s != tolower(styler.SafeGetCharAt(currentPos + n)))
336 return false;
337 s++;
339 return true;
342 //--------------------------------------------------------------------------------------------------
344 // Store both the current line's fold level and the next lines in the
345 // level store to make it easy to pick up with each increment.
346 static void FoldMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *[], Accessor &styler)
348 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
349 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
350 bool foldOnlyBegin = styler.GetPropertyInt("fold.sql.only.begin", 0) != 0;
352 int visibleChars = 0;
353 int lineCurrent = styler.GetLine(startPos);
354 int levelCurrent = SC_FOLDLEVELBASE;
355 if (lineCurrent > 0)
356 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
357 int levelNext = levelCurrent;
359 int styleNext = styler.StyleAt(startPos);
360 int style = initStyle;
361 int activeState = (style == SCE_MYSQL_HIDDENCOMMAND) ? HIDDENCOMMAND_STATE : style & HIDDENCOMMAND_STATE;
363 bool endPending = false;
364 bool whenPending = false;
365 bool elseIfPending = false;
367 char nextChar = styler.SafeGetCharAt(startPos);
368 for (unsigned int i = startPos; length > 0; i++, length--)
370 int stylePrev = style;
371 int lastActiveState = activeState;
372 style = styleNext;
373 styleNext = styler.StyleAt(i + 1);
374 activeState = (style == SCE_MYSQL_HIDDENCOMMAND) ? HIDDENCOMMAND_STATE : style & HIDDENCOMMAND_STATE;
376 char currentChar = nextChar;
377 nextChar = styler.SafeGetCharAt(i + 1);
378 bool atEOL = (currentChar == '\r' && nextChar != '\n') || (currentChar == '\n');
380 switch (MASKACTIVE(style))
382 case SCE_MYSQL_COMMENT:
383 if (foldComment)
385 // Multiline comment style /* .. */ just started or is still in progress.
386 if (IsStreamCommentStyle(style) && !IsStreamCommentStyle(stylePrev))
387 levelNext++;
389 break;
390 case SCE_MYSQL_COMMENTLINE:
391 if (foldComment)
393 // Not really a standard, but we add support for single line comments
394 // with special curly braces syntax as foldable comments too.
395 // MySQL needs -- comments to be followed by space or control char
396 if (styler.Match(i, "--"))
398 char chNext2 = styler.SafeGetCharAt(i + 2);
399 char chNext3 = styler.SafeGetCharAt(i + 3);
400 if (chNext2 == '{' || chNext3 == '{')
401 levelNext++;
402 else
403 if (chNext2 == '}' || chNext3 == '}')
404 levelNext--;
407 break;
408 case SCE_MYSQL_HIDDENCOMMAND:
410 if (endPending)
412 // A conditional command is not a white space so it should end the current block
413 // before opening a new one.
414 endPending = false;
415 levelNext--;
416 if (levelNext < SC_FOLDLEVELBASE)
417 levelNext = SC_FOLDLEVELBASE;
420 if (activeState != lastActiveState)
421 levelNext++;
422 break;
423 case SCE_MYSQL_OPERATOR:
424 if (endPending)
426 endPending = false;
427 levelNext--;
428 if (levelNext < SC_FOLDLEVELBASE)
429 levelNext = SC_FOLDLEVELBASE;
431 if (currentChar == '(')
432 levelNext++;
433 else
434 if (currentChar == ')')
436 levelNext--;
437 if (levelNext < SC_FOLDLEVELBASE)
438 levelNext = SC_FOLDLEVELBASE;
440 break;
441 case SCE_MYSQL_MAJORKEYWORD:
442 case SCE_MYSQL_KEYWORD:
443 case SCE_MYSQL_FUNCTION:
444 case SCE_MYSQL_PROCEDUREKEYWORD:
445 // Reserved and other keywords.
446 if (style != stylePrev)
448 // END decreases the folding level, regardless which keyword follows.
449 bool endFound = MatchIgnoreCase(styler, i, "end");
450 if (endPending)
452 levelNext--;
453 if (levelNext < SC_FOLDLEVELBASE)
454 levelNext = SC_FOLDLEVELBASE;
456 else
457 if (!endFound)
459 if (MatchIgnoreCase(styler, i, "begin"))
460 levelNext++;
461 else
463 if (!foldOnlyBegin)
465 bool whileFound = MatchIgnoreCase(styler, i, "while");
466 bool loopFound = MatchIgnoreCase(styler, i, "loop");
467 bool repeatFound = MatchIgnoreCase(styler, i, "repeat");
468 bool caseFound = MatchIgnoreCase(styler, i, "case");
470 if (whileFound || loopFound || repeatFound || caseFound)
471 levelNext++;
472 else
474 // IF alone does not increase the fold level as it is also used in non-block'ed
475 // code like DROP PROCEDURE blah IF EXISTS.
476 // Instead THEN opens the new level (if not part of an ELSEIF or WHEN (case) branch).
477 if (MatchIgnoreCase(styler, i, "then"))
479 if (!elseIfPending && !whenPending)
480 levelNext++;
481 else
483 elseIfPending = false;
484 whenPending = false;
487 else
489 // Neither of if/then/while/loop/repeat/case, so check for
490 // sub parts of IF and CASE.
491 if (MatchIgnoreCase(styler, i, "elseif"))
492 elseIfPending = true;
493 if (MatchIgnoreCase(styler, i, "when"))
494 whenPending = true;
501 // Keep the current end state for the next round.
502 endPending = endFound;
504 break;
506 default:
507 if (!isspacechar(currentChar) && endPending)
509 // END followed by a non-whitespace character (not covered by other cases like identifiers)
510 // also should end a folding block. Typical case: END followed by self defined delimiter.
511 levelNext--;
512 if (levelNext < SC_FOLDLEVELBASE)
513 levelNext = SC_FOLDLEVELBASE;
515 break;
518 // Go up one level if we just ended a multi line comment.
519 if (IsStreamCommentStyle(stylePrev) && !IsStreamCommentStyle(style))
521 levelNext--;
522 if (levelNext < SC_FOLDLEVELBASE)
523 levelNext = SC_FOLDLEVELBASE;
526 if (activeState == 0 && lastActiveState != 0)
528 // Decrease fold level when we left a hidden command.
529 levelNext--;
530 if (levelNext < SC_FOLDLEVELBASE)
531 levelNext = SC_FOLDLEVELBASE;
534 if (atEOL)
536 // Apply the new folding level to this line.
537 // Leave pending states as they are otherwise a line break will de-sync
538 // code folding and valid syntax.
539 int levelUse = levelCurrent;
540 int lev = levelUse | levelNext << 16;
541 if (visibleChars == 0 && foldCompact)
542 lev |= SC_FOLDLEVELWHITEFLAG;
543 if (levelUse < levelNext)
544 lev |= SC_FOLDLEVELHEADERFLAG;
545 if (lev != styler.LevelAt(lineCurrent))
546 styler.SetLevel(lineCurrent, lev);
548 lineCurrent++;
549 levelCurrent = levelNext;
550 visibleChars = 0;
553 if (!isspacechar(currentChar))
554 visibleChars++;
558 //--------------------------------------------------------------------------------------------------
560 static const char * const mysqlWordListDesc[] = {
561 "Major Keywords",
562 "Keywords",
563 "Database Objects",
564 "Functions",
565 "System Variables",
566 "Procedure keywords",
567 "User Keywords 1",
568 "User Keywords 2",
569 "User Keywords 3",
573 LexerModule lmMySQL(SCLEX_MYSQL, ColouriseMySQLDoc, "mysql", FoldMySQLDoc, mysqlWordListDesc);