1 // Scintilla source code edit control
3 ** Lexer for C++, C, Java, and JavaScript.
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.
18 #include "StyleContext.h"
20 #include "Scintilla.h"
22 #include "CharacterSet.h"
25 using namespace Scintilla
;
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
);
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
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
;
45 char ch
= styler
[pos
];
46 if (ch
== '+' || ch
== '-') {
47 return styler
[pos
- 1] == ch
;
53 static void ColouriseCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
54 Accessor
&styler
, bool caseSensitive
) {
56 WordList
&keywords
= *keywordlists
[0];
57 WordList
&keywords2
= *keywordlists
[1];
58 WordList
&keywords3
= *keywordlists
[2];
59 WordList
&keywords4
= *keywordlists
[3];
61 bool stylingWithinPreprocessor
= styler
.GetPropertyInt("styling.within.preprocessor") != 0;
63 CharacterSet
setOKBeforeRE(CharacterSet::setNone
, "([{=,:;!%^&*|?~+-");
64 CharacterSet
setCouldBePostOp(CharacterSet::setNone
, "+-");
66 CharacterSet
setDoxygen(CharacterSet::setLower
, "$@\\&<>#{}[]");
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('$');
75 int chPrevNonWhite
= ' ';
77 bool lastWordWasUUID
= false;
78 int styleBeforeDCKeyword
= SCE_C_DEFAULT
;
79 bool continuationLine
= false;
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
;
93 continuationLine
= lineEndChar
== '\\';
97 // look back to set chPrevNonWhite properly for better regex colouring
100 while (--back
&& IsSpaceEquiv(styler
.StyleAt(back
)))
102 if (styler
.StyleAt(back
) == SCE_C_OPERATOR
) {
103 chPrevNonWhite
= styler
.SafeGetCharAt(back
);
107 StyleContext
sc(startPos
, length
, initStyle
, styler
);
109 for (; sc
.More(); sc
.Forward()) {
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
);
117 // Reset states to begining of colourise so no surprises
118 // if different sets of lines lexed.
120 lastWordWasUUID
= false;
123 // Handle line continuation generically.
125 if (sc
.chNext
== '\n' || sc
.chNext
== '\r') {
127 if (sc
.ch
== '\r' && sc
.chNext
== '\n') {
130 continuationLine
= true;
135 // Determine if the current state should terminate.
138 sc
.SetState(SCE_C_DEFAULT
);
141 // We accept almost anything because of hex. and number suffixes
142 if (!setWord
.Contains(sc
.ch
)) {
143 sc
.SetState(SCE_C_DEFAULT
);
146 case SCE_C_IDENTIFIER
:
147 if (!setWord
.Contains(sc
.ch
) || (sc
.ch
== '.')) {
150 sc
.GetCurrent(s
, sizeof(s
));
152 sc
.GetCurrentLowered(s
, sizeof(s
));
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
);
162 sc
.SetState(SCE_C_DEFAULT
);
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
);
173 if (sc
.Match('/', '*') || sc
.Match('/', '/')) {
174 sc
.SetState(SCE_C_DEFAULT
);
179 if (sc
.Match('*', '/')) {
181 sc
.ForwardSetState(SCE_C_DEFAULT
);
184 case SCE_C_COMMENTDOC
:
185 if (sc
.Match('*', '/')) {
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
);
196 case SCE_C_COMMENTLINE
:
197 if (sc
.atLineStart
) {
198 sc
.SetState(SCE_C_DEFAULT
);
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
);
212 case SCE_C_COMMENTDOCKEYWORD
:
213 if ((styleBeforeDCKeyword
== SCE_C_COMMENTDOC
) && sc
.Match('*', '/')) {
214 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
216 sc
.ForwardSetState(SCE_C_DEFAULT
);
217 } else if (!setDoxygen
.Contains(sc
.ch
)) {
220 sc
.GetCurrent(s
, sizeof(s
));
222 sc
.GetCurrentLowered(s
, sizeof(s
));
224 if (!IsASpace(sc
.ch
) || !keywords3
.InList(s
+ 1)) {
225 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
227 sc
.SetState(styleBeforeDCKeyword
);
232 sc
.ChangeState(SCE_C_STRINGEOL
);
233 } else if (sc
.ch
== '\\') {
234 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
237 } else if (sc
.ch
== '\"') {
238 sc
.ForwardSetState(SCE_C_DEFAULT
);
241 case SCE_C_CHARACTER
:
243 sc
.ChangeState(SCE_C_STRINGEOL
);
244 } else if (sc
.ch
== '\\') {
245 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
248 } else if (sc
.ch
== '\'') {
249 sc
.ForwardSetState(SCE_C_DEFAULT
);
253 if (sc
.atLineStart
) {
254 sc
.SetState(SCE_C_DEFAULT
);
255 } else if (sc
.ch
== '/') {
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
== '/') {
267 case SCE_C_STRINGEOL
:
268 if (sc
.atLineStart
) {
269 sc
.SetState(SCE_C_DEFAULT
);
274 if (sc
.chNext
== '\"') {
277 sc
.ForwardSetState(SCE_C_DEFAULT
);
282 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== ')') {
283 sc
.SetState(SCE_C_DEFAULT
);
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
);
292 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
293 if (lastWordWasUUID
) {
294 sc
.SetState(SCE_C_UUID
);
295 lastWordWasUUID
= false;
297 sc
.SetState(SCE_C_NUMBER
);
299 } else if (setWordStart
.Contains(sc
.ch
) || (sc
.ch
== '@')) {
300 if (lastWordWasUUID
) {
301 sc
.SetState(SCE_C_UUID
);
302 lastWordWasUUID
= false;
304 sc
.SetState(SCE_C_IDENTIFIER
);
306 } else if (sc
.Match('/', '*')) {
307 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Qt/Doxygen doc. style
308 sc
.SetState(SCE_C_COMMENTDOC
);
310 sc
.SetState(SCE_C_COMMENT
);
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
);
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
332 } while ((sc
.ch
== ' ' || sc
.ch
== '\t') && sc
.More());
334 sc
.SetState(SCE_C_DEFAULT
);
336 } else if (isoperator(static_cast<char>(sc
.ch
))) {
337 sc
.SetState(SCE_C_OPERATOR
);
341 if (!IsASpace(sc
.ch
) && !IsSpaceEquiv(sc
.state
)) {
342 chPrevNonWhite
= sc
.ch
;
345 continuationLine
= false;
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
;
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
;
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
++) {
379 chNext
= styler
.SafeGetCharAt(i
+ 1);
380 int stylePrev
= style
;
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
)) {
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.
392 if (foldComment
&& (style
== SCE_C_COMMENTLINE
)) {
393 if ((ch
== '/') && (chNext
== '/')) {
394 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
395 if (chNext2
== '{') {
397 } else if (chNext2
== '}') {
402 if (foldPreprocessor
&& (style
== SCE_C_PREPROCESSOR
)) {
404 unsigned int j
= i
+ 1;
405 while ((j
< endPos
) && IsASpaceOrTab(styler
.SafeGetCharAt(j
))) {
408 if (styler
.Match(j
, "region") || styler
.Match(j
, "if")) {
410 } else if (styler
.Match(j
, "end")) {
415 if (style
== SCE_C_OPERATOR
) {
417 // Measure the minimum before a '{' to allow
418 // folding on "} else {"
419 if (levelMinCurrent
> levelNext
) {
420 levelMinCurrent
= levelNext
;
423 } else if (ch
== '}') {
428 int levelUse
= levelCurrent
;
430 levelUse
= levelMinCurrent
;
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
);
441 levelCurrent
= levelNext
;
442 levelMinCurrent
= levelCurrent
;
450 static const char * const cppWordLists
[] = {
451 "Primary keywords and identifiers",
452 "Secondary keywords and identifiers",
453 "Documentation comment keywords",
455 "Global classes and typedefs",
459 static void ColouriseCppDocSensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
461 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, true);
464 static void ColouriseCppDocInsensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
466 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, false);
469 LexerModule
lmCPP(SCLEX_CPP
, ColouriseCppDocSensitive
, "cpp", FoldCppDoc
, cppWordLists
);
470 LexerModule
lmCPPNoCase(SCLEX_CPPNOCASE
, ColouriseCppDocInsensitive
, "cppnocase", FoldCppDoc
, cppWordLists
);