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 | |
30 | using namespace Lexilla; |
31 | |
32 | static bool isCmakeNumber(char ch) |
33 | { |
34 | return(ch >= '0' && ch <= '9'); |
35 | } |
36 | |
37 | static bool isCmakeChar(char ch) |
38 | { |
39 | return(ch == '.' ) || (ch == '_' ) || isCmakeNumber(ch) || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); |
40 | } |
41 | |
42 | static bool isCmakeLetter(char ch) |
43 | { |
44 | return(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); |
45 | } |
46 | |
47 | static 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 | |
75 | static 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 | |
105 | static 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 | |
167 | static 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 | |
382 | static 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 | |
452 | static const char * const cmakeWordLists[] = { |
453 | "Commands" , |
454 | "Parameters" , |
455 | "UserDefined" , |
456 | 0, |
457 | 0,}; |
458 | |
459 | LexerModule lmCmake(SCLEX_CMAKE, ColouriseCmakeDoc, "cmake" , FoldCmakeDoc, cmakeWordLists); |
460 | |