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