| 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 | |
| 30 | using namespace Lexilla; |
| 31 | |
| 32 | static bool innoIsBlank(int ch) { |
| 33 | return (ch == ' ') || (ch == '\t'); |
| 34 | } |
| 35 | |
| 36 | static 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 | |
| 53 | static 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 = false; |
| 63 | enum section{None, Code, Messages}; |
| 64 | |
| 65 | WordList §ionKeywords = *keywordLists[0]; |
| 66 | WordList &standardKeywords = *keywordLists[1]; |
| 67 | WordList ¶meterKeywords = *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 | |
| 299 | static const char * const innoWordListDesc[] = { |
| 300 | "Sections" , |
| 301 | "Keywords" , |
| 302 | "Parameters" , |
| 303 | "Preprocessor directives" , |
| 304 | "Pascal keywords" , |
| 305 | "User defined keywords" , |
| 306 | 0 |
| 307 | }; |
| 308 | |
| 309 | static 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 | |
| 348 | LexerModule lmInno(SCLEX_INNOSETUP, ColouriseInnoDoc, "inno" , FoldInnoDoc, innoWordListDesc); |
| 349 | |