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