updated Scintilla to 2.29
[TortoiseGit.git] / ext / scintilla / lexers / LexMySQL.cxx
blobdd483c19ef3543fbe1504222dc141563bb88defd
1 /**
2 * Scintilla source code edit control
3 * @file LexMySQL.cxx
4 * Lexer for MySQL
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.
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[])
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;
94 //--------------------------------------------------------------------------------------------------
96 static void ColouriseMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
97 Accessor &styler)
99 StyleContext sc(startPos, length, initStyle, styler);
101 for (; sc.More(); sc.Forward())
103 // Determine if the current state should terminate.
104 switch (sc.state)
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))
118 CheckForKeyword(sc, keywordlists);
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);
125 sc.SetState(SCE_MYSQL_DEFAULT);
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))
135 int length = sc.LengthCurrent() + 1;
136 char* s = new char[length];
137 sc.GetCurrentLowered(s, length);
139 // Check for known system variables here.
140 if (keywordlists[4]->InList(&s[2]))
141 sc.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE);
142 delete [] s;
144 sc.SetState(SCE_MYSQL_DEFAULT);
146 break;
147 case SCE_MYSQL_QUOTEDIDENTIFIER:
148 if (sc.ch == '`')
150 if (sc.chNext == '`')
151 sc.Forward(); // Ignore it
152 else
153 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
155 break;
156 case SCE_MYSQL_COMMENT:
157 case SCE_MYSQL_HIDDENCOMMAND:
158 if (sc.Match('*', '/'))
160 sc.Forward();
161 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
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 == '\'')
174 // End of single quoted string reached?
175 if (sc.chNext == '\'')
176 sc.Forward();
177 else
178 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
180 break;
181 case SCE_MYSQL_DQSTRING:
182 if (sc.ch == '\\')
183 sc.Forward(); // Escape sequence
184 else
185 if (sc.ch == '\"')
187 // End of single quoted string reached?
188 if (sc.chNext == '\"')
189 sc.Forward();
190 else
191 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
193 break;
196 // Determine if a new state should be entered.
197 if (sc.state == SCE_MYSQL_DEFAULT)
199 switch (sc.ch)
201 case '@':
202 if (sc.chNext == '@')
204 sc.SetState(SCE_MYSQL_SYSTEMVARIABLE);
205 sc.Forward(2); // Skip past @@.
207 else
208 if (IsAWordStart(sc.ch))
210 sc.SetState(SCE_MYSQL_VARIABLE);
211 sc.Forward(); // Skip past @.
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('/', '*'))
237 sc.SetState(SCE_MYSQL_COMMENT);
239 // Skip comment introducer and check for hidden command.
240 sc.Forward(2);
241 if (sc.ch == '!')
243 sc.ChangeState(SCE_MYSQL_HIDDENCOMMAND);
244 sc.Forward();
247 else
248 if (sc.Match("--"))
250 // Special MySQL single line comment.
251 sc.SetState(SCE_MYSQL_COMMENTLINE);
252 sc.Forward(2);
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);
258 else
259 if (isoperator(static_cast<char>(sc.ch)))
260 sc.SetState(SCE_MYSQL_OPERATOR);
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)
269 CheckForKeyword(sc, keywordlists);
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);
277 sc.Complete();
280 //--------------------------------------------------------------------------------------------------
283 * Helper function to determine if we have a foldable comment currently.
285 static bool IsStreamCommentStyle(int style)
287 return style == SCE_MYSQL_COMMENT;
290 //--------------------------------------------------------------------------------------------------
293 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
294 * companion to Match()...
296 bool MatchIgnoreCase(Accessor &styler, int currentPos, const char *s)
298 for (int n = 0; *s; n++)
300 if (*s != tolower(styler.SafeGetCharAt(currentPos + n)))
301 return false;
302 s++;
304 return true;
307 //--------------------------------------------------------------------------------------------------
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)
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;
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;
324 int styleNext = styler.StyleAt(startPos);
325 int style = initStyle;
327 bool endPending = false;
328 bool whenPending = false;
329 bool elseIfPending = false;
331 char nextChar = styler.SafeGetCharAt(startPos);
332 for (unsigned int i = startPos; length > 0; i++, length--)
334 int stylePrev = style;
335 style = styleNext;
336 styleNext = styler.StyleAt(i + 1);
338 char currentChar = nextChar;
339 nextChar = styler.SafeGetCharAt(i + 1);
340 bool atEOL = (currentChar == '\r' && nextChar != '\n') || (currentChar == '\n');
342 switch (style)
344 case SCE_MYSQL_COMMENT:
345 if (foldComment)
347 // Multiline comment style /* .. */.
348 if (IsStreamCommentStyle(style))
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--;
360 break;
361 case SCE_MYSQL_COMMENTLINE:
362 if (foldComment)
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, "--"))
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--;
378 break;
379 case SCE_MYSQL_HIDDENCOMMAND:
380 if (endPending)
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;
389 if (style != stylePrev)
390 levelNext++;
391 else
392 if (style != styleNext)
394 levelNext--;
395 if (levelNext < SC_FOLDLEVELBASE)
396 levelNext = SC_FOLDLEVELBASE;
398 break;
399 case SCE_MYSQL_OPERATOR:
400 if (endPending)
402 endPending = false;
403 levelNext--;
404 if (levelNext < SC_FOLDLEVELBASE)
405 levelNext = SC_FOLDLEVELBASE;
407 if (currentChar == '(')
408 levelNext++;
409 else
410 if (currentChar == ')')
412 levelNext--;
413 if (levelNext < SC_FOLDLEVELBASE)
414 levelNext = SC_FOLDLEVELBASE;
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)
424 // END decreases the folding level, regardless which keyword follows.
425 bool endFound = MatchIgnoreCase(styler, i, "end");
426 if (endPending)
428 levelNext--;
429 if (levelNext < SC_FOLDLEVELBASE)
430 levelNext = SC_FOLDLEVELBASE;
432 else
433 if (!endFound)
435 if (MatchIgnoreCase(styler, i, "begin"))
436 levelNext++;
437 else
439 if (!foldOnlyBegin)
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");
446 if (whileFound || loopFound || repeatFound || caseFound)
447 levelNext++;
448 else
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"))
455 if (!elseIfPending && !whenPending)
456 levelNext++;
457 else
459 elseIfPending = false;
460 whenPending = false;
463 else
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;
477 // Keep the current end state for the next round.
478 endPending = endFound;
480 break;
482 default:
483 if (!isspace(currentChar) && endPending)
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;
491 break;
494 if (atEOL)
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);
508 lineCurrent++;
509 levelCurrent = levelNext;
510 visibleChars = 0;
513 if (!isspacechar(currentChar))
514 visibleChars++;
518 //--------------------------------------------------------------------------------------------------
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",
533 LexerModule lmMySQL(SCLEX_MYSQL, ColouriseMySQLDoc, "mysql", FoldMySQLDoc, mysqlWordListDesc);