]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/LexCPP.cxx
Update Scintilla to version 1.75
[wxWidgets.git] / src / stc / scintilla / src / LexCPP.cxx
1 // Scintilla source code edit control
2 /** @file LexCPP.cxx
3 ** Lexer for C++, C, Java, and JavaScript.
4 **/
5 // Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdarg.h>
13
14 #include "Platform.h"
15
16 #include "PropSet.h"
17 #include "Accessor.h"
18 #include "StyleContext.h"
19 #include "KeyWords.h"
20 #include "Scintilla.h"
21 #include "SciLexer.h"
22 #include "CharacterSet.h"
23
24 #ifdef SCI_NAMESPACE
25 using namespace Scintilla;
26 #endif
27
28 static bool IsSpaceEquiv(int state) {
29 return (state <= SCE_C_COMMENTDOC) ||
30 // including SCE_C_DEFAULT, SCE_C_COMMENT, SCE_C_COMMENTLINE
31 (state == SCE_C_COMMENTLINEDOC) || (state == SCE_C_COMMENTDOCKEYWORD) ||
32 (state == SCE_C_COMMENTDOCKEYWORDERROR);
33 }
34
35 // Preconditions: sc.currentPos points to a character after '+' or '-'.
36 // The test for pos reaching 0 should be redundant,
37 // and is in only for safety measures.
38 // Limitation: this code will give the incorrect answer for code like
39 // a = b+++/ptn/...
40 // Putting a space between the '++' post-inc operator and the '+' binary op
41 // fixes this, and is highly recommended for readability anyway.
42 static bool FollowsPostfixOperator(StyleContext &sc, Accessor &styler) {
43 int pos = (int) sc.currentPos;
44 while (--pos > 0) {
45 char ch = styler[pos];
46 if (ch == '+' || ch == '-') {
47 return styler[pos - 1] == ch;
48 }
49 }
50 return false;
51 }
52
53 static void ColouriseCppDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
54 Accessor &styler, bool caseSensitive) {
55
56 WordList &keywords = *keywordlists[0];
57 WordList &keywords2 = *keywordlists[1];
58 WordList &keywords3 = *keywordlists[2];
59 WordList &keywords4 = *keywordlists[3];
60
61 bool stylingWithinPreprocessor = styler.GetPropertyInt("styling.within.preprocessor") != 0;
62
63 CharacterSet setOKBeforeRE(CharacterSet::setNone, "([{=,:;!%^&*|?~+-");
64 CharacterSet setCouldBePostOp(CharacterSet::setNone, "+-");
65
66 CharacterSet setDoxygen(CharacterSet::setLower, "$@\\&<>#{}[]");
67
68 CharacterSet setWordStart(CharacterSet::setAlpha, "_", 0x80, true);
69 CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
70 if (styler.GetPropertyInt("lexer.cpp.allow.dollars", 1) != 0) {
71 setWordStart.Add('$');
72 setWord.Add('$');
73 }
74
75 int chPrevNonWhite = ' ';
76 int visibleChars = 0;
77 bool lastWordWasUUID = false;
78 int styleBeforeDCKeyword = SCE_C_DEFAULT;
79 bool continuationLine = false;
80
81 if (initStyle == SCE_C_PREPROCESSOR) {
82 // Set continuationLine if last character of previous line is '\'
83 int lineCurrent = styler.GetLine(startPos);
84 if (lineCurrent > 0) {
85 int chBack = styler.SafeGetCharAt(startPos-1, 0);
86 int chBack2 = styler.SafeGetCharAt(startPos-2, 0);
87 int lineEndChar = '!';
88 if (chBack2 == '\r' && chBack == '\n') {
89 lineEndChar = styler.SafeGetCharAt(startPos-3, 0);
90 } else if (chBack == '\n' || chBack == '\r') {
91 lineEndChar = chBack2;
92 }
93 continuationLine = lineEndChar == '\\';
94 }
95 }
96
97 // look back to set chPrevNonWhite properly for better regex colouring
98 if (startPos > 0) {
99 int back = startPos;
100 while (--back && IsSpaceEquiv(styler.StyleAt(back)))
101 ;
102 if (styler.StyleAt(back) == SCE_C_OPERATOR) {
103 chPrevNonWhite = styler.SafeGetCharAt(back);
104 }
105 }
106
107 StyleContext sc(startPos, length, initStyle, styler);
108
109 for (; sc.More(); sc.Forward()) {
110
111 if (sc.atLineStart) {
112 if (sc.state == SCE_C_STRING) {
113 // Prevent SCE_C_STRINGEOL from leaking back to previous line which
114 // ends with a line continuation by locking in the state upto this position.
115 sc.SetState(SCE_C_STRING);
116 }
117 // Reset states to begining of colourise so no surprises
118 // if different sets of lines lexed.
119 visibleChars = 0;
120 lastWordWasUUID = false;
121 }
122
123 // Handle line continuation generically.
124 if (sc.ch == '\\') {
125 if (sc.chNext == '\n' || sc.chNext == '\r') {
126 sc.Forward();
127 if (sc.ch == '\r' && sc.chNext == '\n') {
128 sc.Forward();
129 }
130 continuationLine = true;
131 continue;
132 }
133 }
134
135 // Determine if the current state should terminate.
136 switch (sc.state) {
137 case SCE_C_OPERATOR:
138 sc.SetState(SCE_C_DEFAULT);
139 break;
140 case SCE_C_NUMBER:
141 // We accept almost anything because of hex. and number suffixes
142 if (!setWord.Contains(sc.ch)) {
143 sc.SetState(SCE_C_DEFAULT);
144 }
145 break;
146 case SCE_C_IDENTIFIER:
147 if (!setWord.Contains(sc.ch) || (sc.ch == '.')) {
148 char s[1000];
149 if (caseSensitive) {
150 sc.GetCurrent(s, sizeof(s));
151 } else {
152 sc.GetCurrentLowered(s, sizeof(s));
153 }
154 if (keywords.InList(s)) {
155 lastWordWasUUID = strcmp(s, "uuid") == 0;
156 sc.ChangeState(SCE_C_WORD);
157 } else if (keywords2.InList(s)) {
158 sc.ChangeState(SCE_C_WORD2);
159 } else if (keywords4.InList(s)) {
160 sc.ChangeState(SCE_C_GLOBALCLASS);
161 }
162 sc.SetState(SCE_C_DEFAULT);
163 }
164 break;
165 case SCE_C_PREPROCESSOR:
166 if (sc.atLineStart && !continuationLine) {
167 sc.SetState(SCE_C_DEFAULT);
168 } else if (stylingWithinPreprocessor) {
169 if (IsASpace(sc.ch)) {
170 sc.SetState(SCE_C_DEFAULT);
171 }
172 } else {
173 if (sc.Match('/', '*') || sc.Match('/', '/')) {
174 sc.SetState(SCE_C_DEFAULT);
175 }
176 }
177 break;
178 case SCE_C_COMMENT:
179 if (sc.Match('*', '/')) {
180 sc.Forward();
181 sc.ForwardSetState(SCE_C_DEFAULT);
182 }
183 break;
184 case SCE_C_COMMENTDOC:
185 if (sc.Match('*', '/')) {
186 sc.Forward();
187 sc.ForwardSetState(SCE_C_DEFAULT);
188 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
189 // Verify that we have the conditions to mark a comment-doc-keyword
190 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
191 styleBeforeDCKeyword = SCE_C_COMMENTDOC;
192 sc.SetState(SCE_C_COMMENTDOCKEYWORD);
193 }
194 }
195 break;
196 case SCE_C_COMMENTLINE:
197 if (sc.atLineStart) {
198 sc.SetState(SCE_C_DEFAULT);
199 }
200 break;
201 case SCE_C_COMMENTLINEDOC:
202 if (sc.atLineStart) {
203 sc.SetState(SCE_C_DEFAULT);
204 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
205 // Verify that we have the conditions to mark a comment-doc-keyword
206 if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) {
207 styleBeforeDCKeyword = SCE_C_COMMENTLINEDOC;
208 sc.SetState(SCE_C_COMMENTDOCKEYWORD);
209 }
210 }
211 break;
212 case SCE_C_COMMENTDOCKEYWORD:
213 if ((styleBeforeDCKeyword == SCE_C_COMMENTDOC) && sc.Match('*', '/')) {
214 sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR);
215 sc.Forward();
216 sc.ForwardSetState(SCE_C_DEFAULT);
217 } else if (!setDoxygen.Contains(sc.ch)) {
218 char s[100];
219 if (caseSensitive) {
220 sc.GetCurrent(s, sizeof(s));
221 } else {
222 sc.GetCurrentLowered(s, sizeof(s));
223 }
224 if (!IsASpace(sc.ch) || !keywords3.InList(s + 1)) {
225 sc.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR);
226 }
227 sc.SetState(styleBeforeDCKeyword);
228 }
229 break;
230 case SCE_C_STRING:
231 if (sc.atLineEnd) {
232 sc.ChangeState(SCE_C_STRINGEOL);
233 } else if (sc.ch == '\\') {
234 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
235 sc.Forward();
236 }
237 } else if (sc.ch == '\"') {
238 sc.ForwardSetState(SCE_C_DEFAULT);
239 }
240 break;
241 case SCE_C_CHARACTER:
242 if (sc.atLineEnd) {
243 sc.ChangeState(SCE_C_STRINGEOL);
244 } else if (sc.ch == '\\') {
245 if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
246 sc.Forward();
247 }
248 } else if (sc.ch == '\'') {
249 sc.ForwardSetState(SCE_C_DEFAULT);
250 }
251 break;
252 case SCE_C_REGEX:
253 if (sc.atLineStart) {
254 sc.SetState(SCE_C_DEFAULT);
255 } else if (sc.ch == '/') {
256 sc.Forward();
257 while ((sc.ch < 0x80) && islower(sc.ch))
258 sc.Forward(); // gobble regex flags
259 sc.SetState(SCE_C_DEFAULT);
260 } else if (sc.ch == '\\') {
261 // Gobble up the quoted character
262 if (sc.chNext == '\\' || sc.chNext == '/') {
263 sc.Forward();
264 }
265 }
266 break;
267 case SCE_C_STRINGEOL:
268 if (sc.atLineStart) {
269 sc.SetState(SCE_C_DEFAULT);
270 }
271 break;
272 case SCE_C_VERBATIM:
273 if (sc.ch == '\"') {
274 if (sc.chNext == '\"') {
275 sc.Forward();
276 } else {
277 sc.ForwardSetState(SCE_C_DEFAULT);
278 }
279 }
280 break;
281 case SCE_C_UUID:
282 if (sc.ch == '\r' || sc.ch == '\n' || sc.ch == ')') {
283 sc.SetState(SCE_C_DEFAULT);
284 }
285 }
286
287 // Determine if a new state should be entered.
288 if (sc.state == SCE_C_DEFAULT) {
289 if (sc.Match('@', '\"')) {
290 sc.SetState(SCE_C_VERBATIM);
291 sc.Forward();
292 } else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
293 if (lastWordWasUUID) {
294 sc.SetState(SCE_C_UUID);
295 lastWordWasUUID = false;
296 } else {
297 sc.SetState(SCE_C_NUMBER);
298 }
299 } else if (setWordStart.Contains(sc.ch) || (sc.ch == '@')) {
300 if (lastWordWasUUID) {
301 sc.SetState(SCE_C_UUID);
302 lastWordWasUUID = false;
303 } else {
304 sc.SetState(SCE_C_IDENTIFIER);
305 }
306 } else if (sc.Match('/', '*')) {
307 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Qt/Doxygen doc. style
308 sc.SetState(SCE_C_COMMENTDOC);
309 } else {
310 sc.SetState(SCE_C_COMMENT);
311 }
312 sc.Forward(); // Eat the * so it isn't used for the end of the comment
313 } else if (sc.Match('/', '/')) {
314 if ((sc.Match("///") && !sc.Match("////")) || sc.Match("//!"))
315 // Support of Qt/Doxygen doc. style
316 sc.SetState(SCE_C_COMMENTLINEDOC);
317 else
318 sc.SetState(SCE_C_COMMENTLINE);
319 } else if (sc.ch == '/' && setOKBeforeRE.Contains(chPrevNonWhite) &&
320 (!setCouldBePostOp.Contains(chPrevNonWhite) || !FollowsPostfixOperator(sc, styler))) {
321 sc.SetState(SCE_C_REGEX); // JavaScript's RegEx
322 } else if (sc.ch == '\"') {
323 sc.SetState(SCE_C_STRING);
324 } else if (sc.ch == '\'') {
325 sc.SetState(SCE_C_CHARACTER);
326 } else if (sc.ch == '#' && visibleChars == 0) {
327 // Preprocessor commands are alone on their line
328 sc.SetState(SCE_C_PREPROCESSOR);
329 // Skip whitespace between # and preprocessor word
330 do {
331 sc.Forward();
332 } while ((sc.ch == ' ' || sc.ch == '\t') && sc.More());
333 if (sc.atLineEnd) {
334 sc.SetState(SCE_C_DEFAULT);
335 }
336 } else if (isoperator(static_cast<char>(sc.ch))) {
337 sc.SetState(SCE_C_OPERATOR);
338 }
339 }
340
341 if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) {
342 chPrevNonWhite = sc.ch;
343 visibleChars++;
344 }
345 continuationLine = false;
346 }
347 sc.Complete();
348 }
349
350 static bool IsStreamCommentStyle(int style) {
351 return style == SCE_C_COMMENT ||
352 style == SCE_C_COMMENTDOC ||
353 style == SCE_C_COMMENTDOCKEYWORD ||
354 style == SCE_C_COMMENTDOCKEYWORDERROR;
355 }
356
357 // Store both the current line's fold level and the next lines in the
358 // level store to make it easy to pick up with each increment
359 // and to make it possible to fiddle the current level for "} else {".
360 static void FoldCppDoc(unsigned int startPos, int length, int initStyle,
361 WordList *[], Accessor &styler) {
362 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
363 bool foldPreprocessor = styler.GetPropertyInt("fold.preprocessor") != 0;
364 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
365 bool foldAtElse = styler.GetPropertyInt("fold.at.else", 0) != 0;
366 unsigned int endPos = startPos + length;
367 int visibleChars = 0;
368 int lineCurrent = styler.GetLine(startPos);
369 int levelCurrent = SC_FOLDLEVELBASE;
370 if (lineCurrent > 0)
371 levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
372 int levelMinCurrent = levelCurrent;
373 int levelNext = levelCurrent;
374 char chNext = styler[startPos];
375 int styleNext = styler.StyleAt(startPos);
376 int style = initStyle;
377 for (unsigned int i = startPos; i < endPos; i++) {
378 char ch = chNext;
379 chNext = styler.SafeGetCharAt(i + 1);
380 int stylePrev = style;
381 style = styleNext;
382 styleNext = styler.StyleAt(i + 1);
383 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
384 if (foldComment && IsStreamCommentStyle(style)) {
385 if (!IsStreamCommentStyle(stylePrev) && (stylePrev != SCE_C_COMMENTLINEDOC)) {
386 levelNext++;
387 } else if (!IsStreamCommentStyle(styleNext) && (styleNext != SCE_C_COMMENTLINEDOC) && !atEOL) {
388 // Comments don't end at end of line and the next character may be unstyled.
389 levelNext--;
390 }
391 }
392 if (foldComment && (style == SCE_C_COMMENTLINE)) {
393 if ((ch == '/') && (chNext == '/')) {
394 char chNext2 = styler.SafeGetCharAt(i + 2);
395 if (chNext2 == '{') {
396 levelNext++;
397 } else if (chNext2 == '}') {
398 levelNext--;
399 }
400 }
401 }
402 if (foldPreprocessor && (style == SCE_C_PREPROCESSOR)) {
403 if (ch == '#') {
404 unsigned int j = i + 1;
405 while ((j < endPos) && IsASpaceOrTab(styler.SafeGetCharAt(j))) {
406 j++;
407 }
408 if (styler.Match(j, "region") || styler.Match(j, "if")) {
409 levelNext++;
410 } else if (styler.Match(j, "end")) {
411 levelNext--;
412 }
413 }
414 }
415 if (style == SCE_C_OPERATOR) {
416 if (ch == '{') {
417 // Measure the minimum before a '{' to allow
418 // folding on "} else {"
419 if (levelMinCurrent > levelNext) {
420 levelMinCurrent = levelNext;
421 }
422 levelNext++;
423 } else if (ch == '}') {
424 levelNext--;
425 }
426 }
427 if (atEOL) {
428 int levelUse = levelCurrent;
429 if (foldAtElse) {
430 levelUse = levelMinCurrent;
431 }
432 int lev = levelUse | levelNext << 16;
433 if (visibleChars == 0 && foldCompact)
434 lev |= SC_FOLDLEVELWHITEFLAG;
435 if (levelUse < levelNext)
436 lev |= SC_FOLDLEVELHEADERFLAG;
437 if (lev != styler.LevelAt(lineCurrent)) {
438 styler.SetLevel(lineCurrent, lev);
439 }
440 lineCurrent++;
441 levelCurrent = levelNext;
442 levelMinCurrent = levelCurrent;
443 visibleChars = 0;
444 }
445 if (!IsASpace(ch))
446 visibleChars++;
447 }
448 }
449
450 static const char * const cppWordLists[] = {
451 "Primary keywords and identifiers",
452 "Secondary keywords and identifiers",
453 "Documentation comment keywords",
454 "Unused",
455 "Global classes and typedefs",
456 0,
457 };
458
459 static void ColouriseCppDocSensitive(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
460 Accessor &styler) {
461 ColouriseCppDoc(startPos, length, initStyle, keywordlists, styler, true);
462 }
463
464 static void ColouriseCppDocInsensitive(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
465 Accessor &styler) {
466 ColouriseCppDoc(startPos, length, initStyle, keywordlists, styler, false);
467 }
468
469 LexerModule lmCPP(SCLEX_CPP, ColouriseCppDocSensitive, "cpp", FoldCppDoc, cppWordLists);
470 LexerModule lmCPPNoCase(SCLEX_CPPNOCASE, ColouriseCppDocInsensitive, "cppnocase", FoldCppDoc, cppWordLists);