1 // Scintilla source code edit control
2 /** @file LexCmake.cxx
3 ** Lexer for Cmake
4 **/
5 // Copyright 2007 by Cristian Adam <cristian [dot] adam [at] gmx [dot] net>
6 // based on the NSIS lexer
7 // The License.txt file describes the conditions under which this software may be distributed.
8
9 #include <stdlib.h>
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdarg.h>
13 #include <assert.h>
14 #include <ctype.h>
15
16 #include "ILexer.h"
17 #include "Scintilla.h"
18 #include "SciLexer.h"
19
20 #include "WordList.h"
21 #include "LexAccessor.h"
22 #include "Accessor.h"
23 #include "StyleContext.h"
24 #include "CharacterSet.h"
25 #include "LexerModule.h"
26
27 using namespace Scintilla;
28
isCmakeNumber(char ch)29 static bool isCmakeNumber(char ch)
30 {
31 return(ch >= '0' && ch <= '9');
32 }
33
isCmakeChar(char ch)34 static bool isCmakeChar(char ch)
35 {
36 return(ch == '.' ) || (ch == '_' ) || isCmakeNumber(ch) || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
37 }
38
isCmakeLetter(char ch)39 static bool isCmakeLetter(char ch)
40 {
41 return(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
42 }
43
CmakeNextLineHasElse(Sci_PositionU start,Sci_PositionU end,Accessor & styler)44 static bool CmakeNextLineHasElse(Sci_PositionU start, Sci_PositionU end, Accessor &styler)
45 {
46 Sci_Position nNextLine = -1;
47 for ( Sci_PositionU i = start; i < end; i++ ) {
48 char cNext = styler.SafeGetCharAt( i );
49 if ( cNext == '\n' ) {
50 nNextLine = i+1;
51 break;
52 }
53 }
54
55 if ( nNextLine == -1 ) // We never foudn the next line...
56 return false;
57
58 for ( Sci_PositionU firstChar = nNextLine; firstChar < end; firstChar++ ) {
59 char cNext = styler.SafeGetCharAt( firstChar );
60 if ( cNext == ' ' )
61 continue;
62 if ( cNext == '\t' )
63 continue;
64 if ( styler.Match(firstChar, "ELSE") || styler.Match(firstChar, "else"))
65 return true;
66 break;
67 }
68
69 return false;
70 }
71
calculateFoldCmake(Sci_PositionU start,Sci_PositionU end,int foldlevel,Accessor & styler,bool bElse)72 static int calculateFoldCmake(Sci_PositionU start, Sci_PositionU end, int foldlevel, Accessor &styler, bool bElse)
73 {
74 // If the word is too long, it is not what we are looking for
75 if ( end - start > 20 )
76 return foldlevel;
77
78 int newFoldlevel = foldlevel;
79
80 char s[20]; // The key word we are looking for has atmost 13 characters
81 for (unsigned int i = 0; i < end - start + 1 && i < 19; i++) {
82 s[i] = static_cast<char>( styler[ start + i ] );
83 s[i + 1] = '\0';
84 }
85
86 if ( CompareCaseInsensitive(s, "IF") == 0 || CompareCaseInsensitive(s, "WHILE") == 0
87 || CompareCaseInsensitive(s, "MACRO") == 0 || CompareCaseInsensitive(s, "FOREACH") == 0
88 || CompareCaseInsensitive(s, "FUNCTION") == 0 || CompareCaseInsensitive(s, "ELSEIF") == 0)
89 newFoldlevel++;
90 else if ( CompareCaseInsensitive(s, "ENDIF") == 0 || CompareCaseInsensitive(s, "ENDWHILE") == 0
91 || CompareCaseInsensitive(s, "ENDMACRO") == 0 || CompareCaseInsensitive(s, "ENDFOREACH") == 0
92 || CompareCaseInsensitive(s, "ENDFUNCTION") == 0)
93 newFoldlevel--;
94 else if ( bElse && CompareCaseInsensitive(s, "ELSEIF") == 0 )
95 newFoldlevel++;
96 else if ( bElse && CompareCaseInsensitive(s, "ELSE") == 0 )
97 newFoldlevel++;
98
99 return newFoldlevel;
100 }
101
classifyWordCmake(Sci_PositionU start,Sci_PositionU end,WordList * keywordLists[],Accessor & styler)102 static int classifyWordCmake(Sci_PositionU start, Sci_PositionU end, WordList *keywordLists[], Accessor &styler )
103 {
104 char word[100] = {0};
105 char lowercaseWord[100] = {0};
106
107 WordList &Commands = *keywordLists[0];
108 WordList &Parameters = *keywordLists[1];
109 WordList &UserDefined = *keywordLists[2];
110
111 for (Sci_PositionU i = 0; i < end - start + 1 && i < 99; i++) {
112 word[i] = static_cast<char>( styler[ start + i ] );
113 lowercaseWord[i] = static_cast<char>(tolower(word[i]));
114 }
115
116 // Check for special words...
117 if ( CompareCaseInsensitive(word, "MACRO") == 0 || CompareCaseInsensitive(word, "ENDMACRO") == 0 )
118 return SCE_CMAKE_MACRODEF;
119
120 if ( CompareCaseInsensitive(word, "IF") == 0 || CompareCaseInsensitive(word, "ENDIF") == 0 )
121 return SCE_CMAKE_IFDEFINEDEF;
122
123 if ( CompareCaseInsensitive(word, "ELSEIF") == 0 || CompareCaseInsensitive(word, "ELSE") == 0 )
124 return SCE_CMAKE_IFDEFINEDEF;
125
126 if ( CompareCaseInsensitive(word, "WHILE") == 0 || CompareCaseInsensitive(word, "ENDWHILE") == 0)
127 return SCE_CMAKE_WHILEDEF;
128
129 if ( CompareCaseInsensitive(word, "FOREACH") == 0 || CompareCaseInsensitive(word, "ENDFOREACH") == 0)
130 return SCE_CMAKE_FOREACHDEF;
131
132 if ( Commands.InList(lowercaseWord) )
133 return SCE_CMAKE_COMMANDS;
134
135 if ( Parameters.InList(word) )
136 return SCE_CMAKE_PARAMETERS;
137
138
139 if ( UserDefined.InList(word) )
140 return SCE_CMAKE_USERDEFINED;
141
142 if ( strlen(word) > 3 ) {
143 if ( word[1] == '{' && word[strlen(word)-1] == '}' )
144 return SCE_CMAKE_VARIABLE;
145 }
146
147 // To check for numbers
148 if ( isCmakeNumber( word[0] ) ) {
149 bool bHasSimpleCmakeNumber = true;
150 for (unsigned int j = 1; j < end - start + 1 && j < 99; j++) {
151 if ( !isCmakeNumber( word[j] ) ) {
152 bHasSimpleCmakeNumber = false;
153 break;
154 }
155 }
156
157 if ( bHasSimpleCmakeNumber )
158 return SCE_CMAKE_NUMBER;
159 }
160
161 return SCE_CMAKE_DEFAULT;
162 }
163
ColouriseCmakeDoc(Sci_PositionU startPos,Sci_Position length,int,WordList * keywordLists[],Accessor & styler)164 static void ColouriseCmakeDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *keywordLists[], Accessor &styler)
165 {
166 int state = SCE_CMAKE_DEFAULT;
167 if ( startPos > 0 )
168 state = styler.StyleAt(startPos-1); // Use the style from the previous line, usually default, but could be commentbox
169
170 styler.StartAt( startPos );
171 styler.GetLine( startPos );
172
173 Sci_PositionU nLengthDoc = startPos + length;
174 styler.StartSegment( startPos );
175
176 char cCurrChar;
177 bool bVarInString = false;
178 bool bClassicVarInString = false;
179
180 Sci_PositionU i;
181 for ( i = startPos; i < nLengthDoc; i++ ) {
182 cCurrChar = styler.SafeGetCharAt( i );
183 char cNextChar = styler.SafeGetCharAt(i+1);
184
185 switch (state) {
186 case SCE_CMAKE_DEFAULT:
187 if ( cCurrChar == '#' ) { // we have a comment line
188 styler.ColourTo(i-1, state );
189 state = SCE_CMAKE_COMMENT;
190 break;
191 }
192 if ( cCurrChar == '"' ) {
193 styler.ColourTo(i-1, state );
194 state = SCE_CMAKE_STRINGDQ;
195 bVarInString = false;
196 bClassicVarInString = false;
197 break;
198 }
199 if ( cCurrChar == '\'' ) {
200 styler.ColourTo(i-1, state );
201 state = SCE_CMAKE_STRINGRQ;
202 bVarInString = false;
203 bClassicVarInString = false;
204 break;
205 }
206 if ( cCurrChar == '`' ) {
207 styler.ColourTo(i-1, state );
208 state = SCE_CMAKE_STRINGLQ;
209 bVarInString = false;
210 bClassicVarInString = false;
211 break;
212 }
213
214 // CMake Variable
215 if ( cCurrChar == '$' || isCmakeChar(cCurrChar)) {
216 styler.ColourTo(i-1,state);
217 state = SCE_CMAKE_VARIABLE;
218
219 // If it is a number, we must check and set style here first...
220 if ( isCmakeNumber(cCurrChar) && (cNextChar == '\t' || cNextChar == ' ' || cNextChar == '\r' || cNextChar == '\n' ) )
221 styler.ColourTo( i, SCE_CMAKE_NUMBER);
222
223 break;
224 }
225
226 break;
227 case SCE_CMAKE_COMMENT:
228 if ( cCurrChar == '\n' || cCurrChar == '\r' ) {
229 if ( styler.SafeGetCharAt(i-1) == '\\' ) {
230 styler.ColourTo(i-2,state);
231 styler.ColourTo(i-1,SCE_CMAKE_DEFAULT);
232 }
233 else {
234 styler.ColourTo(i-1,state);
235 state = SCE_CMAKE_DEFAULT;
236 }
237 }
238 break;
239 case SCE_CMAKE_STRINGDQ:
240 case SCE_CMAKE_STRINGLQ:
241 case SCE_CMAKE_STRINGRQ:
242
243 if ( styler.SafeGetCharAt(i-1) == '\\' && styler.SafeGetCharAt(i-2) == '$' )
244 break; // Ignore the next character, even if it is a quote of some sort
245
246 if ( cCurrChar == '"' && state == SCE_CMAKE_STRINGDQ ) {
247 styler.ColourTo(i,state);
248 state = SCE_CMAKE_DEFAULT;
249 break;
250 }
251
252 if ( cCurrChar == '`' && state == SCE_CMAKE_STRINGLQ ) {
253 styler.ColourTo(i,state);
254 state = SCE_CMAKE_DEFAULT;
255 break;
256 }
257
258 if ( cCurrChar == '\'' && state == SCE_CMAKE_STRINGRQ ) {
259 styler.ColourTo(i,state);
260 state = SCE_CMAKE_DEFAULT;
261 break;
262 }
263
264 if ( cNextChar == '\r' || cNextChar == '\n' ) {
265 Sci_Position nCurLine = styler.GetLine(i+1);
266 Sci_Position nBack = i;
267 // We need to check if the previous line has a \ in it...
268 bool bNextLine = false;
269
270 while ( nBack > 0 ) {
271 if ( styler.GetLine(nBack) != nCurLine )
272 break;
273
274 char cTemp = styler.SafeGetCharAt(nBack, 'a'); // Letter 'a' is safe here
275
276 if ( cTemp == '\\' ) {
277 bNextLine = true;
278 break;
279 }
280 if ( cTemp != '\r' && cTemp != '\n' && cTemp != '\t' && cTemp != ' ' )
281 break;
282
283 nBack--;
284 }
285
286 if ( bNextLine ) {
287 styler.ColourTo(i+1,state);
288 }
289 if ( bNextLine == false ) {
290 styler.ColourTo(i,state);
291 state = SCE_CMAKE_DEFAULT;
292 }
293 }
294 break;
295
296 case SCE_CMAKE_VARIABLE:
297
298 // CMake Variable:
299 if ( cCurrChar == '$' )
300 state = SCE_CMAKE_DEFAULT;
301 else if ( cCurrChar == '\\' && (cNextChar == 'n' || cNextChar == 'r' || cNextChar == 't' ) )
302 state = SCE_CMAKE_DEFAULT;
303 else if ( (isCmakeChar(cCurrChar) && !isCmakeChar( cNextChar) && cNextChar != '}') || cCurrChar == '}' ) {
304 state = classifyWordCmake( styler.GetStartSegment(), i, keywordLists, styler );
305 styler.ColourTo( i, state);
306 state = SCE_CMAKE_DEFAULT;
307 }
308 else if ( !isCmakeChar( cCurrChar ) && cCurrChar != '{' && cCurrChar != '}' ) {
309 if ( classifyWordCmake( styler.GetStartSegment(), i-1, keywordLists, styler) == SCE_CMAKE_NUMBER )
310 styler.ColourTo( i-1, SCE_CMAKE_NUMBER );
311
312 state = SCE_CMAKE_DEFAULT;
313
314 if ( cCurrChar == '"' ) {
315 state = SCE_CMAKE_STRINGDQ;
316 bVarInString = false;
317 bClassicVarInString = false;
318 }
319 else if ( cCurrChar == '`' ) {
320 state = SCE_CMAKE_STRINGLQ;
321 bVarInString = false;
322 bClassicVarInString = false;
323 }
324 else if ( cCurrChar == '\'' ) {
325 state = SCE_CMAKE_STRINGRQ;
326 bVarInString = false;
327 bClassicVarInString = false;
328 }
329 else if ( cCurrChar == '#' ) {
330 state = SCE_CMAKE_COMMENT;
331 }
332 }
333 break;
334 }
335
336 if ( state == SCE_CMAKE_STRINGDQ || state == SCE_CMAKE_STRINGLQ || state == SCE_CMAKE_STRINGRQ ) {
337 bool bIngoreNextDollarSign = false;
338
339 if ( bVarInString && cCurrChar == '$' ) {
340 bVarInString = false;
341 bIngoreNextDollarSign = true;
342 }
343 else if ( bVarInString && cCurrChar == '\\' && (cNextChar == 'n' || cNextChar == 'r' || cNextChar == 't' || cNextChar == '"' || cNextChar == '`' || cNextChar == '\'' ) ) {
344 styler.ColourTo( i+1, SCE_CMAKE_STRINGVAR);
345 bVarInString = false;
346 bIngoreNextDollarSign = false;
347 }
348
349 else if ( bVarInString && !isCmakeChar(cNextChar) ) {
350 int nWordState = classifyWordCmake( styler.GetStartSegment(), i, keywordLists, styler);
351 if ( nWordState == SCE_CMAKE_VARIABLE )
352 styler.ColourTo( i, SCE_CMAKE_STRINGVAR);
353 bVarInString = false;
354 }
355 // Covers "${TEST}..."
356 else if ( bClassicVarInString && cNextChar == '}' ) {
357 styler.ColourTo( i+1, SCE_CMAKE_STRINGVAR);
358 bClassicVarInString = false;
359 }
360
361 // Start of var in string
362 if ( !bIngoreNextDollarSign && cCurrChar == '$' && cNextChar == '{' ) {
363 styler.ColourTo( i-1, state);
364 bClassicVarInString = true;
365 bVarInString = false;
366 }
367 else if ( !bIngoreNextDollarSign && cCurrChar == '$' ) {
368 styler.ColourTo( i-1, state);
369 bVarInString = true;
370 bClassicVarInString = false;
371 }
372 }
373 }
374
375 // Colourise remaining document
376 styler.ColourTo(nLengthDoc-1,state);
377 }
378
FoldCmakeDoc(Sci_PositionU startPos,Sci_Position length,int,WordList * [],Accessor & styler)379 static void FoldCmakeDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler)
380 {
381 // No folding enabled, no reason to continue...
382 if ( styler.GetPropertyInt("fold") == 0 )
383 return;
384
385 bool foldAtElse = styler.GetPropertyInt("fold.at.else", 0) == 1;
386
387 Sci_Position lineCurrent = styler.GetLine(startPos);
388 Sci_PositionU safeStartPos = styler.LineStart( lineCurrent );
389
390 bool bArg1 = true;
391 Sci_Position nWordStart = -1;
392
393 int levelCurrent = SC_FOLDLEVELBASE;
394 if (lineCurrent > 0)
395 levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
396 int levelNext = levelCurrent;
397
398 for (Sci_PositionU i = safeStartPos; i < startPos + length; i++) {
399 char chCurr = styler.SafeGetCharAt(i);
400
401 if ( bArg1 ) {
402 if ( nWordStart == -1 && (isCmakeLetter(chCurr)) ) {
403 nWordStart = i;
404 }
405 else if ( isCmakeLetter(chCurr) == false && nWordStart > -1 ) {
406 int newLevel = calculateFoldCmake( nWordStart, i-1, levelNext, styler, foldAtElse);
407
408 if ( newLevel == levelNext ) {
409 if ( foldAtElse ) {
410 if ( CmakeNextLineHasElse(i, startPos + length, styler) )
411 levelNext--;
412 }
413 }
414 else
415 levelNext = newLevel;
416 bArg1 = false;
417 }
418 }
419
420 if ( chCurr == '\n' ) {
421 if ( bArg1 && foldAtElse) {
422 if ( CmakeNextLineHasElse(i, startPos + length, styler) )
423 levelNext--;
424 }
425
426 // If we are on a new line...
427 int levelUse = levelCurrent;
428 int lev = levelUse | levelNext << 16;
429 if (levelUse < levelNext )
430 lev |= SC_FOLDLEVELHEADERFLAG;
431 if (lev != styler.LevelAt(lineCurrent))
432 styler.SetLevel(lineCurrent, lev);
433
434 lineCurrent++;
435 levelCurrent = levelNext;
436 bArg1 = true; // New line, lets look at first argument again
437 nWordStart = -1;
438 }
439 }
440
441 int levelUse = levelCurrent;
442 int lev = levelUse | levelNext << 16;
443 if (levelUse < levelNext)
444 lev |= SC_FOLDLEVELHEADERFLAG;
445 if (lev != styler.LevelAt(lineCurrent))
446 styler.SetLevel(lineCurrent, lev);
447 }
448
449 static const char * const cmakeWordLists[] = {
450 "Commands",
451 "Parameters",
452 "UserDefined",
453 0,
454 0,};
455
456 LexerModule lmCmake(SCLEX_CMAKE, ColouriseCmakeDoc, "cmake", FoldCmakeDoc, cmakeWordLists);
457