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