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