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