1// Scintilla source code edit control
2/** @file LexInno.cxx
3 ** Lexer for Inno Setup scripts.
4 **/
5// Written by Friedrich Vedder <fvedd@t-online.de>, using code from LexOthers.cxx.
6// Modified by Michael Heath.
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 innoIsBlank(int ch) {
33 return (ch == ' ') || (ch == '\t');
34}
35
36static bool innoNextNotBlankIs(Sci_Position i, Accessor &styler, char needle) {
37 char ch;
38
39 while (i < styler.Length()) {
40 ch = styler.SafeGetCharAt(i);
41
42 if (ch == needle)
43 return true;
44
45 if (!innoIsBlank(ch))
46 return false;
47
48 i++;
49 }
50 return false;
51}
52
53static void ColouriseInnoDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *keywordLists[], Accessor &styler) {
54 int state = SCE_INNO_DEFAULT;
55 char chPrev;
56 char ch = 0;
57 char chNext = styler[startPos];
58 Sci_Position lengthDoc = startPos + length;
59 char *buffer = new char[length+1];
60 Sci_Position bufferCount = 0;
61 bool isBOL, isEOL, isWS, isBOLWS = 0;
62 bool isCStyleComment = false;
63 enum section{None, Code, Messages};
64
65 WordList &sectionKeywords = *keywordLists[0];
66 WordList &standardKeywords = *keywordLists[1];
67 WordList &parameterKeywords = *keywordLists[2];
68 WordList &preprocessorKeywords = *keywordLists[3];
69 WordList &pascalKeywords = *keywordLists[4];
70 WordList &userKeywords = *keywordLists[5];
71
72 Sci_Position curLine = styler.GetLine(startPos);
73 int curLineState = curLine > 0 ? styler.GetLineState(curLine - 1) : 0;
74 bool isCode = (curLineState == section::Code);
75 bool isMessages = (curLineState == section::Messages);
76
77 // Go through all provided text segment
78 // using the hand-written state machine shown below
79 styler.StartAt(startPos);
80 styler.StartSegment(startPos);
81 for (Sci_Position i = startPos; i < lengthDoc; i++) {
82 chPrev = ch;
83 ch = chNext;
84 chNext = styler.SafeGetCharAt(i + 1);
85
86 if (styler.IsLeadByte(ch)) {
87 chNext = styler.SafeGetCharAt(i + 2);
88 i++;
89 continue;
90 }
91
92 isBOL = (chPrev == 0) || (chPrev == '\n') || (chPrev == '\r' && ch != '\n');
93 isBOLWS = (isBOL) ? 1 : (isBOLWS && (chPrev == ' ' || chPrev == '\t'));
94 isEOL = (ch == '\n' || ch == '\r');
95 isWS = (ch == ' ' || ch == '\t');
96
97 if ((ch == '\r' && chNext != '\n') || (ch == '\n')) {
98 // Remember the line state for future incremental lexing
99 curLine = styler.GetLine(i);
100
101 if (isCode) {
102 styler.SetLineState(curLine, section::Code);
103 } else if (isMessages) {
104 styler.SetLineState(curLine, section::Messages);
105 } else {
106 styler.SetLineState(curLine, section::None);
107 }
108 }
109
110 switch(state) {
111 case SCE_INNO_DEFAULT:
112 if (!isCode && ch == ';' && isBOLWS) {
113 // Start of a comment
114 state = SCE_INNO_COMMENT;
115 } else if (ch == '[' && isBOLWS) {
116 // Start of a section name
117 bufferCount = 0;
118 state = SCE_INNO_SECTION;
119 } else if (ch == '#' && isBOLWS) {
120 // Start of a preprocessor directive
121 state = SCE_INNO_PREPROC;
122 } else if (!isCode && ch == '{' && chNext != '{' && chPrev != '{') {
123 // Start of an inline expansion
124 state = SCE_INNO_INLINE_EXPANSION;
125 } else if (isCode && (ch == '{' || (ch == '(' && chNext == '*'))) {
126 // Start of a Pascal comment
127 state = SCE_INNO_COMMENT_PASCAL;
128 isCStyleComment = false;
129 } else if (isCode && ch == '/' && chNext == '/') {
130 // Apparently, C-style comments are legal, too
131 state = SCE_INNO_COMMENT_PASCAL;
132 isCStyleComment = true;
133 } else if (!isMessages && ch == '"') {
134 // Start of a double-quote string
135 state = SCE_INNO_STRING_DOUBLE;
136 } else if (!isMessages && ch == '\'') {
137 // Start of a single-quote string
138 state = SCE_INNO_STRING_SINGLE;
139 } else if (!isMessages && IsASCII(ch) && (isalpha(ch) || (ch == '_'))) {
140 // Start of an identifier
141 bufferCount = 0;
142 buffer[bufferCount++] = static_cast<char>(tolower(ch));
143 state = SCE_INNO_IDENTIFIER;
144 } else {
145 // Style it the default style
146 styler.ColourTo(i,SCE_INNO_DEFAULT);
147 }
148 break;
149
150 case SCE_INNO_COMMENT:
151 if (isEOL) {
152 state = SCE_INNO_DEFAULT;
153 styler.ColourTo(i-1,SCE_INNO_COMMENT);
154
155 // Push back the faulty character
156 chNext = styler[i--];
157 ch = chPrev;
158 }
159 break;
160
161 case SCE_INNO_IDENTIFIER:
162 if (IsASCII(ch) && (isalnum(ch) || (ch == '_'))) {
163 buffer[bufferCount++] = static_cast<char>(tolower(ch));
164 } else {
165 state = SCE_INNO_DEFAULT;
166 buffer[bufferCount] = '\0';
167
168 // Check if the buffer contains a keyword
169 if (!isCode && standardKeywords.InList(buffer) && innoNextNotBlankIs(i, styler, '=')) {
170 styler.ColourTo(i-1,SCE_INNO_KEYWORD);
171 } else if (!isCode && parameterKeywords.InList(buffer) && innoNextNotBlankIs(i, styler, ':')) {
172 styler.ColourTo(i-1,SCE_INNO_PARAMETER);
173 } else if (isCode && pascalKeywords.InList(buffer)) {
174 styler.ColourTo(i-1,SCE_INNO_KEYWORD_PASCAL);
175 } else if (!isCode && userKeywords.InList(buffer)) {
176 styler.ColourTo(i-1,SCE_INNO_KEYWORD_USER);
177 } else {
178 styler.ColourTo(i-1,SCE_INNO_DEFAULT);
179 }
180
181 // Push back the faulty character
182 chNext = styler[i--];
183 ch = chPrev;
184 }
185 break;
186
187 case SCE_INNO_SECTION:
188 if (ch == ']') {
189 state = SCE_INNO_DEFAULT;
190 buffer[bufferCount] = '\0';
191
192 // Check if the buffer contains a section name
193 if (sectionKeywords.InList(buffer)) {
194 styler.ColourTo(i,SCE_INNO_SECTION);
195 isCode = !CompareCaseInsensitive(buffer, "code");
196
197 isMessages = isCode ? false : (
198 !CompareCaseInsensitive(buffer, "custommessages")
199 || !CompareCaseInsensitive(buffer, "messages"));
200 } else {
201 styler.ColourTo(i,SCE_INNO_DEFAULT);
202 }
203 } else if (IsASCII(ch) && (isalnum(ch) || (ch == '_'))) {
204 buffer[bufferCount++] = static_cast<char>(tolower(ch));
205 } else {
206 state = SCE_INNO_DEFAULT;
207 styler.ColourTo(i,SCE_INNO_DEFAULT);
208 }
209 break;
210
211 case SCE_INNO_PREPROC:
212 if (isWS || isEOL) {
213 if (IsASCII(chPrev) && isalpha(chPrev)) {
214 state = SCE_INNO_DEFAULT;
215 buffer[bufferCount] = '\0';
216
217 // Check if the buffer contains a preprocessor directive
218 if (preprocessorKeywords.InList(buffer)) {
219 styler.ColourTo(i-1,SCE_INNO_PREPROC);
220 } else {
221 styler.ColourTo(i-1,SCE_INNO_DEFAULT);
222 }
223
224 // Push back the faulty character
225 chNext = styler[i--];
226 ch = chPrev;
227 }
228 } else if (IsASCII(ch) && isalpha(ch)) {
229 if (chPrev == '#' || chPrev == ' ' || chPrev == '\t')
230 bufferCount = 0;
231 buffer[bufferCount++] = static_cast<char>(tolower(ch));
232 }
233 break;
234
235 case SCE_INNO_STRING_DOUBLE:
236 if (ch == '"') {
237 state = SCE_INNO_DEFAULT;
238 styler.ColourTo(i,SCE_INNO_STRING_DOUBLE);
239 } else if (isEOL) {
240 state = SCE_INNO_DEFAULT;
241 styler.ColourTo(i-1,SCE_INNO_STRING_DOUBLE);
242
243 // Push back the faulty character
244 chNext = styler[i--];
245 ch = chPrev;
246 }
247 break;
248
249 case SCE_INNO_STRING_SINGLE:
250 if (ch == '\'') {
251 state = SCE_INNO_DEFAULT;
252 styler.ColourTo(i,SCE_INNO_STRING_SINGLE);
253 } else if (isEOL) {
254 state = SCE_INNO_DEFAULT;
255 styler.ColourTo(i-1,SCE_INNO_STRING_SINGLE);
256
257 // Push back the faulty character
258 chNext = styler[i--];
259 ch = chPrev;
260 }
261 break;
262
263 case SCE_INNO_INLINE_EXPANSION:
264 if (ch == '}') {
265 state = SCE_INNO_DEFAULT;
266 styler.ColourTo(i,SCE_INNO_INLINE_EXPANSION);
267 } else if (isEOL) {
268 state = SCE_INNO_DEFAULT;
269 styler.ColourTo(i,SCE_INNO_DEFAULT);
270 }
271 break;
272
273 case SCE_INNO_COMMENT_PASCAL:
274 if (isCStyleComment) {
275 if (isEOL) {
276 state = SCE_INNO_DEFAULT;
277 styler.ColourTo(i-1,SCE_INNO_COMMENT_PASCAL);
278
279 // Push back the faulty character
280 chNext = styler[i--];
281 ch = chPrev;
282 }
283 } else {
284 if (ch == '}' || (ch == ')' && chPrev == '*')) {
285 state = SCE_INNO_DEFAULT;
286 styler.ColourTo(i,SCE_INNO_COMMENT_PASCAL);
287 } else if (isEOL) {
288 state = SCE_INNO_DEFAULT;
289 styler.ColourTo(i,SCE_INNO_DEFAULT);
290 }
291 }
292 break;
293
294 }
295 }
296 delete []buffer;
297}
298
299static const char * const innoWordListDesc[] = {
300 "Sections",
301 "Keywords",
302 "Parameters",
303 "Preprocessor directives",
304 "Pascal keywords",
305 "User defined keywords",
306 0
307};
308
309static void FoldInnoDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) {
310 Sci_PositionU endPos = startPos + length;
311 char chNext = styler[startPos];
312
313 Sci_Position lineCurrent = styler.GetLine(startPos);
314
315 bool sectionFlag = false;
316 int levelPrev = lineCurrent > 0 ? styler.LevelAt(lineCurrent - 1) : SC_FOLDLEVELBASE;
317 int level;
318
319 for (Sci_PositionU i = startPos; i < endPos; i++) {
320 char ch = chNext;
321 chNext = styler[i+1];
322 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
323 int style = styler.StyleAt(i);
324
325 if (style == SCE_INNO_SECTION)
326 sectionFlag = true;
327
328 if (atEOL || i == endPos - 1) {
329 if (sectionFlag) {
330 level = SC_FOLDLEVELBASE | SC_FOLDLEVELHEADERFLAG;
331 if (level == levelPrev)
332 styler.SetLevel(lineCurrent - 1, levelPrev & ~SC_FOLDLEVELHEADERFLAG);
333 } else {
334 level = levelPrev & SC_FOLDLEVELNUMBERMASK;
335 if (levelPrev & SC_FOLDLEVELHEADERFLAG)
336 level++;
337 }
338
339 styler.SetLevel(lineCurrent, level);
340
341 levelPrev = level;
342 lineCurrent++;
343 sectionFlag = false;
344 }
345 }
346}
347
348LexerModule lmInno(SCLEX_INNOSETUP, ColouriseInnoDoc, "inno", FoldInnoDoc, innoWordListDesc);
349