1// Scintilla source code edit control 2/** @file LexNsis.cxx 3 ** Lexer for NSIS 4 **/ 5// Copyright 2003 - 2005 by Angelo Mandato <angelo [at] spaceblue [dot] com> 6// Last Updated: 03/13/2005 7// The License.txt file describes the conditions under which this software may be distributed. 8#include <stdlib.h> 9#include <string.h> 10#include <ctype.h> 11#include <stdio.h> 12#include <stdarg.h> 13 14#include "Platform.h" 15 16#include "PropSet.h" 17#include "Accessor.h" 18#include "KeyWords.h" 19#include "Scintilla.h" 20#include "SciLexer.h" 21 22/* 23// located in SciLexer.h 24#define SCLEX_NSIS 43 25 26#define SCE_NSIS_DEFAULT 0 27#define SCE_NSIS_COMMENT 1 28#define SCE_NSIS_STRINGDQ 2 29#define SCE_NSIS_STRINGLQ 3 30#define SCE_NSIS_STRINGRQ 4 31#define SCE_NSIS_FUNCTION 5 32#define SCE_NSIS_VARIABLE 6 33#define SCE_NSIS_LABEL 7 34#define SCE_NSIS_USERDEFINED 8 35#define SCE_NSIS_SECTIONDEF 9 36#define SCE_NSIS_SUBSECTIONDEF 10 37#define SCE_NSIS_IFDEFINEDEF 11 38#define SCE_NSIS_MACRODEF 12 39#define SCE_NSIS_STRINGVAR 13 40#define SCE_NSIS_NUMBER 14 41// ADDED for Scintilla v1.63 42#define SCE_NSIS_SECTIONGROUP 15 43#define SCE_NSIS_PAGEEX 16 44#define SCE_NSIS_FUNCTIONDEF 17 45#define SCE_NSIS_COMMENTBOX 18 46*/ 47 48static bool isNsisNumber(char ch) 49{ 50 return (ch >= '0' && ch <= '9'); 51} 52 53static bool isNsisChar(char ch) 54{ 55 return (ch == '.' ) || (ch == '_' ) || isNsisNumber(ch) || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); 56} 57 58static bool isNsisLetter(char ch) 59{ 60 return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); 61} 62 63static bool NsisNextLineHasElse(unsigned int start, unsigned int end, Accessor &styler) 64{ 65 int nNextLine = -1; 66 for( unsigned int i = start; i < end; i++ ) 67 { 68 char cNext = styler.SafeGetCharAt( i ); 69 if( cNext == '\n' ) 70 { 71 nNextLine = i+1; 72 break; 73 } 74 } 75 76 if( nNextLine == -1 ) // We never foudn the next line... 77 return false; 78 79 for( unsigned int firstChar = nNextLine; firstChar < end; firstChar++ ) 80 { 81 char cNext = styler.SafeGetCharAt( firstChar ); 82 if( cNext == ' ' ) 83 continue; 84 if( cNext == '\t' ) 85 continue; 86 if( cNext == '!' ) 87 { 88 if( styler.Match(firstChar, "!else") ) 89 return true; 90 } 91 break; 92 } 93 94 return false; 95} 96 97static int NsisCmp( char *s1, char *s2, bool bIgnoreCase ) 98{ 99 if( bIgnoreCase ) 100 return CompareCaseInsensitive( s1, s2); 101 102 return strcmp( s1, s2 ); 103} 104 105static int calculateFoldNsis(unsigned int start, unsigned int end, int foldlevel, Accessor &styler, bool bElse, bool foldUtilityCmd ) 106{ 107 int style = styler.StyleAt(end); 108 109 // If the word is too long, it is not what we are looking for 110 if( end - start > 20 ) 111 return foldlevel; 112 113 if( foldUtilityCmd ) 114 { 115 // Check the style at this point, if it is not valid, then return zero 116 if( style != SCE_NSIS_FUNCTIONDEF && style != SCE_NSIS_SECTIONDEF && 117 style != SCE_NSIS_SUBSECTIONDEF && style != SCE_NSIS_IFDEFINEDEF && 118 style != SCE_NSIS_MACRODEF && style != SCE_NSIS_SECTIONGROUP && 119 style != SCE_NSIS_PAGEEX ) 120 return foldlevel; 121 } 122 else 123 { 124 if( style != SCE_NSIS_FUNCTIONDEF && style != SCE_NSIS_SECTIONDEF && 125 style != SCE_NSIS_SUBSECTIONDEF && style != SCE_NSIS_SECTIONGROUP && 126 style != SCE_NSIS_PAGEEX ) 127 return foldlevel; 128 } 129 130 int newFoldlevel = foldlevel; 131 bool bIgnoreCase = false; 132 if( styler.GetPropertyInt("nsis.ignorecase") == 1 ) 133 bIgnoreCase = true; 134 135 char s[20]; // The key word we are looking for has atmost 13 characters 136 for (unsigned int i = 0; i < end - start + 1 && i < 19; i++) 137 { 138 s[i] = static_cast<char>( styler[ start + i ] ); 139 s[i + 1] = '\0'; 140 } 141 142 if( s[0] == '!' ) 143 { 144 if( NsisCmp(s, "!ifndef", bIgnoreCase) == 0 || NsisCmp(s, "!ifdef", bIgnoreCase ) == 0 || NsisCmp(s, "!macro", bIgnoreCase ) == 0 ) 145 newFoldlevel++; 146 else if( NsisCmp(s, "!endif", bIgnoreCase) == 0 || NsisCmp(s, "!macroend", bIgnoreCase ) == 0 ) 147 newFoldlevel--; 148 else if( bElse && NsisCmp(s, "!else", bIgnoreCase) == 0 ) 149 newFoldlevel++; 150 } 151 else 152 { 153 if( NsisCmp(s, "Section", bIgnoreCase ) == 0 || NsisCmp(s, "SectionGroup", bIgnoreCase ) == 0 || NsisCmp(s, "Function", bIgnoreCase) == 0 || NsisCmp(s, "SubSection", bIgnoreCase ) == 0 || NsisCmp(s, "PageEx", bIgnoreCase ) == 0 ) 154 newFoldlevel++; 155 else if( NsisCmp(s, "SectionGroupEnd", bIgnoreCase ) == 0 || NsisCmp(s, "SubSectionEnd", bIgnoreCase ) == 0 || NsisCmp(s, "FunctionEnd", bIgnoreCase) == 0 || NsisCmp(s, "SectionEnd", bIgnoreCase ) == 0 || NsisCmp(s, "PageExEnd", bIgnoreCase ) == 0 ) 156 newFoldlevel--; 157 } 158 159 return newFoldlevel; 160} 161 162static int classifyWordNsis(unsigned int start, unsigned int end, WordList *keywordLists[], Accessor &styler ) 163{ 164 bool bIgnoreCase = false; 165 if( styler.GetPropertyInt("nsis.ignorecase") == 1 ) 166 bIgnoreCase = true; 167 168 bool bUserVars = false; 169 if( styler.GetPropertyInt("nsis.uservars") == 1 ) 170 bUserVars = true; 171 172 char s[100]; 173 174 WordList &Functions = *keywordLists[0]; 175 WordList &Variables = *keywordLists[1]; 176 WordList &Lables = *keywordLists[2]; 177 WordList &UserDefined = *keywordLists[3]; 178 179 for (unsigned int i = 0; i < end - start + 1 && i < 99; i++) 180 { 181 if( bIgnoreCase ) 182 s[i] = static_cast<char>( tolower(styler[ start + i ] ) ); 183 else 184 s[i] = static_cast<char>( styler[ start + i ] ); 185 s[i + 1] = '\0'; 186 } 187 188 // Check for special words... 189 if( NsisCmp(s, "!macro", bIgnoreCase ) == 0 || NsisCmp(s, "!macroend", bIgnoreCase) == 0 ) // Covers !micro and !microend 190 return SCE_NSIS_MACRODEF; 191 192 if( NsisCmp(s, "!ifdef", bIgnoreCase ) == 0 || NsisCmp(s, "!ifndef", bIgnoreCase) == 0 || NsisCmp(s, "!endif", bIgnoreCase) == 0 ) 193 return SCE_NSIS_IFDEFINEDEF; 194 195 if( NsisCmp(s, "!else", bIgnoreCase ) == 0 ) // || NsisCmp(s, "!ifndef", bIgnoreCase) == 0 || NsisCmp(s, "!endif", bIgnoreCase) == 0 ) 196 return SCE_NSIS_IFDEFINEDEF; 197 198 if( NsisCmp(s, "SectionGroup", bIgnoreCase) == 0 || NsisCmp(s, "SectionGroupEnd", bIgnoreCase) == 0 ) // Covers SectionGroup and SectionGroupEnd 199 return SCE_NSIS_SECTIONGROUP; 200 201 if( NsisCmp(s, "Section", bIgnoreCase ) == 0 || NsisCmp(s, "SectionEnd", bIgnoreCase) == 0 ) // Covers Section and SectionEnd 202 return SCE_NSIS_SECTIONDEF; 203 204 if( NsisCmp(s, "SubSection", bIgnoreCase) == 0 || NsisCmp(s, "SubSectionEnd", bIgnoreCase) == 0 ) // Covers SubSection and SubSectionEnd 205 return SCE_NSIS_SUBSECTIONDEF; 206 207 if( NsisCmp(s, "PageEx", bIgnoreCase) == 0 || NsisCmp(s, "PageExEnd", bIgnoreCase) == 0 ) // Covers PageEx and PageExEnd 208 return SCE_NSIS_PAGEEX; 209 210 if( NsisCmp(s, "Function", bIgnoreCase) == 0 || NsisCmp(s, "FunctionEnd", bIgnoreCase) == 0 ) // Covers Function and FunctionEnd 211 return SCE_NSIS_FUNCTIONDEF; 212 213 if ( Functions.InList(s) ) 214 return SCE_NSIS_FUNCTION; 215 216 if ( Variables.InList(s) ) 217 return SCE_NSIS_VARIABLE; 218 219 if ( Lables.InList(s) ) 220 return SCE_NSIS_LABEL; 221 222 if( UserDefined.InList(s) ) 223 return SCE_NSIS_USERDEFINED; 224 225 if( strlen(s) > 3 ) 226 { 227 if( s[1] == '{' && s[strlen(s)-1] == '}' ) 228 return SCE_NSIS_VARIABLE; 229 } 230 231 // See if the variable is a user defined variable 232 if( s[0] == '$' && bUserVars ) 233 { 234 bool bHasSimpleNsisChars = true; 235 for (unsigned int j = 1; j < end - start + 1 && j < 99; j++) 236 { 237 if( !isNsisChar( s[j] ) ) 238 { 239 bHasSimpleNsisChars = false; 240 break; 241 } 242 } 243 244 if( bHasSimpleNsisChars ) 245 return SCE_NSIS_VARIABLE; 246 } 247 248 // To check for numbers 249 if( isNsisNumber( s[0] ) ) 250 { 251 bool bHasSimpleNsisNumber = true; 252 for (unsigned int j = 1; j < end - start + 1 && j < 99; j++) 253 { 254 if( !isNsisNumber( s[j] ) ) 255 { 256 bHasSimpleNsisNumber = false; 257 break; 258 } 259 } 260 261 if( bHasSimpleNsisNumber ) 262 return SCE_NSIS_NUMBER; 263 } 264 265 return SCE_NSIS_DEFAULT; 266} 267 268static void ColouriseNsisDoc(unsigned int startPos, int length, int, WordList *keywordLists[], Accessor &styler) 269{ 270 int state = SCE_NSIS_DEFAULT; 271 if( startPos > 0 ) 272 state = styler.StyleAt(startPos-1); // Use the style from the previous line, usually default, but could be commentbox 273 274 styler.StartAt( startPos ); 275 styler.GetLine( startPos ); 276 277 unsigned int nLengthDoc = startPos + length; 278 styler.StartSegment( startPos ); 279 280 char cCurrChar; 281 bool bVarInString = false; 282 bool bClassicVarInString = false; 283 284 unsigned int i; 285 for( i = startPos; i < nLengthDoc; i++ ) 286 { 287 cCurrChar = styler.SafeGetCharAt( i ); 288 char cNextChar = styler.SafeGetCharAt(i+1); 289 290 switch(state) 291 { 292 case SCE_NSIS_DEFAULT: 293 if( cCurrChar == ';' || cCurrChar == '#' ) // we have a comment line 294 { 295 styler.ColourTo(i-1, state ); 296 state = SCE_NSIS_COMMENT; 297 break; 298 } 299 if( cCurrChar == '"' ) 300 { 301 styler.ColourTo(i-1, state ); 302 state = SCE_NSIS_STRINGDQ; 303 bVarInString = false; 304 bClassicVarInString = false; 305 break; 306 } 307 if( cCurrChar == '\'' ) 308 { 309 styler.ColourTo(i-1, state ); 310 state = SCE_NSIS_STRINGRQ; 311 bVarInString = false; 312 bClassicVarInString = false; 313 break; 314 } 315 if( cCurrChar == '`' ) 316 { 317 styler.ColourTo(i-1, state ); 318 state = SCE_NSIS_STRINGLQ; 319 bVarInString = false; 320 bClassicVarInString = false; 321 break; 322 } 323 324 // NSIS KeyWord,Function, Variable, UserDefined: 325 if( cCurrChar == '$' || isNsisChar(cCurrChar) || cCurrChar == '!' ) 326 { 327 styler.ColourTo(i-1,state); 328 state = SCE_NSIS_FUNCTION; 329 330 // If it is a number, we must check and set style here first... 331 if( isNsisNumber(cCurrChar) && (cNextChar == '\t' || cNextChar == ' ' || cNextChar == '\r' || cNextChar == '\n' ) ) 332 styler.ColourTo( i, SCE_NSIS_NUMBER); 333 334 break; 335 } 336 337 if( cCurrChar == '/' && cNextChar == '*' ) 338 { 339 styler.ColourTo(i-1,state); 340 state = SCE_NSIS_COMMENTBOX; 341 break; 342 } 343 344 break; 345 case SCE_NSIS_COMMENT: 346 if( cNextChar == '\n' || cNextChar == '\r' ) 347 { 348 // Special case: 349 if( cCurrChar == '\\' ) 350 { 351 styler.ColourTo(i-2,state); 352 styler.ColourTo(i,SCE_NSIS_DEFAULT); 353 } 354 else 355 { 356 styler.ColourTo(i,state); 357 state = SCE_NSIS_DEFAULT; 358 } 359 } 360 break; 361 case SCE_NSIS_STRINGDQ: 362 case SCE_NSIS_STRINGLQ: 363 case SCE_NSIS_STRINGRQ: 364 365 if( styler.SafeGetCharAt(i-1) == '\\' && styler.SafeGetCharAt(i-2) == '$' ) 366 break; // Ignore the next character, even if it is a quote of some sort 367 368 if( cCurrChar == '"' && state == SCE_NSIS_STRINGDQ ) 369 { 370 styler.ColourTo(i,state); 371 state = SCE_NSIS_DEFAULT; 372 break; 373 } 374 375 if( cCurrChar == '`' && state == SCE_NSIS_STRINGLQ ) 376 { 377 styler.ColourTo(i,state); 378 state = SCE_NSIS_DEFAULT; 379 break; 380 } 381 382 if( cCurrChar == '\'' && state == SCE_NSIS_STRINGRQ ) 383 { 384 styler.ColourTo(i,state); 385 state = SCE_NSIS_DEFAULT; 386 break; 387 } 388 389 if( cNextChar == '\r' || cNextChar == '\n' ) 390 { 391 int nCurLine = styler.GetLine(i+1); 392 int nBack = i; 393 // We need to check if the previous line has a \ in it... 394 bool bNextLine = false; 395 396 while( nBack > 0 ) 397 { 398 if( styler.GetLine(nBack) != nCurLine ) 399 break; 400 401 char cTemp = styler.SafeGetCharAt(nBack, 'a'); // Letter 'a' is safe here 402 403 if( cTemp == '\\' ) 404 { 405 bNextLine = true; 406 break; 407 } 408 if( cTemp != '\r' && cTemp != '\n' && cTemp != '\t' && cTemp != ' ' ) 409 break; 410 411 nBack--; 412 } 413 414 if( bNextLine ) 415 { 416 styler.ColourTo(i+1,state); 417 } 418 if( bNextLine == false ) 419 { 420 styler.ColourTo(i,state); 421 state = SCE_NSIS_DEFAULT; 422 } 423 } 424 break; 425 426 case SCE_NSIS_FUNCTION: 427 428 // NSIS KeyWord: 429 if( cCurrChar == '$' ) 430 state = SCE_NSIS_DEFAULT; 431 else if( cCurrChar == '\\' && (cNextChar == 'n' || cNextChar == 'r' || cNextChar == 't' ) ) 432 state = SCE_NSIS_DEFAULT; 433 else if( (isNsisChar(cCurrChar) && !isNsisChar( cNextChar) && cNextChar != '}') || cCurrChar == '}' ) 434 { 435 state = classifyWordNsis( styler.GetStartSegment(), i, keywordLists, styler ); 436 styler.ColourTo( i, state); 437 state = SCE_NSIS_DEFAULT; 438 } 439 else if( !isNsisChar( cCurrChar ) && cCurrChar != '{' && cCurrChar != '}' ) 440 { 441 if( classifyWordNsis( styler.GetStartSegment(), i-1, keywordLists, styler) == SCE_NSIS_NUMBER ) 442 styler.ColourTo( i-1, SCE_NSIS_NUMBER ); 443 444 state = SCE_NSIS_DEFAULT; 445 446 if( cCurrChar == '"' ) 447 { 448 state = SCE_NSIS_STRINGDQ; 449 bVarInString = false; 450 bClassicVarInString = false; 451 } 452 else if( cCurrChar == '`' ) 453 { 454 state = SCE_NSIS_STRINGLQ; 455 bVarInString = false; 456 bClassicVarInString = false; 457 } 458 else if( cCurrChar == '\'' ) 459 { 460 state = SCE_NSIS_STRINGRQ; 461 bVarInString = false; 462 bClassicVarInString = false; 463 } 464 else if( cCurrChar == '#' || cCurrChar == ';' ) 465 { 466 state = SCE_NSIS_COMMENT; 467 } 468 } 469 break; 470 case SCE_NSIS_COMMENTBOX: 471 472 if( styler.SafeGetCharAt(i-1) == '*' && cCurrChar == '/' ) 473 { 474 styler.ColourTo(i,state); 475 state = SCE_NSIS_DEFAULT; 476 } 477 break; 478 } 479 480 if( state == SCE_NSIS_COMMENT || state == SCE_NSIS_COMMENTBOX ) 481 { 482 styler.ColourTo(i,state); 483 } 484 else if( state == SCE_NSIS_STRINGDQ || state == SCE_NSIS_STRINGLQ || state == SCE_NSIS_STRINGRQ ) 485 { 486 bool bIngoreNextDollarSign = false; 487 bool bUserVars = false; 488 if( styler.GetPropertyInt("nsis.uservars") == 1 ) 489 bUserVars = true; 490 491 if( bVarInString && cCurrChar == '$' ) 492 { 493 bVarInString = false; 494 bIngoreNextDollarSign = true; 495 } 496 else if( bVarInString && cCurrChar == '\\' && (cNextChar == 'n' || cNextChar == 'r' || cNextChar == 't' || cNextChar == '"' || cNextChar == '`' || cNextChar == '\'' ) ) 497 { 498 styler.ColourTo( i+1, SCE_NSIS_STRINGVAR); 499 bVarInString = false; 500 bIngoreNextDollarSign = false; 501 } 502 503 // Covers "$INSTDIR and user vars like $MYVAR" 504 else if( bVarInString && !isNsisChar(cNextChar) ) 505 { 506 int nWordState = classifyWordNsis( styler.GetStartSegment(), i, keywordLists, styler); 507 if( nWordState == SCE_NSIS_VARIABLE ) 508 styler.ColourTo( i, SCE_NSIS_STRINGVAR); 509 else if( bUserVars ) 510 styler.ColourTo( i, SCE_NSIS_STRINGVAR); 511 bVarInString = false; 512 } 513 // Covers "${TEST}..." 514 else if( bClassicVarInString && cNextChar == '}' ) 515 { 516 styler.ColourTo( i+1, SCE_NSIS_STRINGVAR); 517 bClassicVarInString = false; 518 } 519 520 // Start of var in string 521 if( !bIngoreNextDollarSign && cCurrChar == '$' && cNextChar == '{' ) 522 { 523 styler.ColourTo( i-1, state); 524 bClassicVarInString = true; 525 bVarInString = false; 526 } 527 else if( !bIngoreNextDollarSign && cCurrChar == '$' ) 528 { 529 styler.ColourTo( i-1, state); 530 bVarInString = true; 531 bClassicVarInString = false; 532 } 533 } 534 } 535 536 // Colourise remaining document 537 styler.ColourTo(nLengthDoc-1,state); 538} 539 540static void FoldNsisDoc(unsigned int startPos, int length, int, WordList *[], Accessor &styler) 541{ 542 // No folding enabled, no reason to continue... 543 if( styler.GetPropertyInt("fold") == 0 ) 544 return; 545 546 bool foldAtElse = styler.GetPropertyInt("fold.at.else", 0) == 1; 547 bool foldUtilityCmd = styler.GetPropertyInt("nsis.foldutilcmd", 1) == 1; 548 bool blockComment = false; 549 550 int lineCurrent = styler.GetLine(startPos); 551 unsigned int safeStartPos = styler.LineStart( lineCurrent ); 552 553 bool bArg1 = true; 554 int nWordStart = -1; 555 556 int levelCurrent = SC_FOLDLEVELBASE; 557 if (lineCurrent > 0) 558 levelCurrent = styler.LevelAt(lineCurrent-1) >> 16; 559 int levelNext = levelCurrent; 560 int style = styler.StyleAt(safeStartPos); 561 if( style == SCE_NSIS_COMMENTBOX ) 562 { 563 if( styler.SafeGetCharAt(safeStartPos) == '/' && styler.SafeGetCharAt(safeStartPos+1) == '*' ) 564 levelNext++; 565 blockComment = true; 566 } 567 568 for (unsigned int i = safeStartPos; i < startPos + length; i++) 569 { 570 char chCurr = styler.SafeGetCharAt(i); 571 style = styler.StyleAt(i); 572 if( blockComment && style != SCE_NSIS_COMMENTBOX ) 573 { 574 levelNext--; 575 blockComment = false; 576 } 577 else if( !blockComment && style == SCE_NSIS_COMMENTBOX ) 578 { 579 levelNext++; 580 blockComment = true; 581 } 582 583 if( bArg1 && !blockComment) 584 { 585 if( nWordStart == -1 && (isNsisLetter(chCurr) || chCurr == '!') ) 586 { 587 nWordStart = i; 588 } 589 else if( isNsisLetter(chCurr) == false && nWordStart > -1 ) 590 { 591 int newLevel = calculateFoldNsis( nWordStart, i-1, levelNext, styler, foldAtElse, foldUtilityCmd ); 592 593 if( newLevel == levelNext ) 594 { 595 if( foldAtElse && foldUtilityCmd ) 596 { 597 if( NsisNextLineHasElse(i, startPos + length, styler) ) 598 levelNext--; 599 } 600 } 601 else 602 levelNext = newLevel; 603 bArg1 = false; 604 } 605 } 606 607 if( chCurr == '\n' ) 608 { 609 if( bArg1 && foldAtElse && foldUtilityCmd && !blockComment ) 610 { 611 if( NsisNextLineHasElse(i, startPos + length, styler) ) 612 levelNext--; 613 } 614 615 // If we are on a new line... 616 int levelUse = levelCurrent; 617 int lev = levelUse | levelNext << 16; 618 if (levelUse < levelNext ) 619 lev |= SC_FOLDLEVELHEADERFLAG; 620 if (lev != styler.LevelAt(lineCurrent)) 621 styler.SetLevel(lineCurrent, lev); 622 623 lineCurrent++; 624 levelCurrent = levelNext; 625 bArg1 = true; // New line, lets look at first argument again 626 nWordStart = -1; 627 } 628 } 629 630 int levelUse = levelCurrent; 631 int lev = levelUse | levelNext << 16; 632 if (levelUse < levelNext) 633 lev |= SC_FOLDLEVELHEADERFLAG; 634 if (lev != styler.LevelAt(lineCurrent)) 635 styler.SetLevel(lineCurrent, lev); 636} 637 638static const char * const nsisWordLists[] = { 639 "Functions", 640 "Variables", 641 "Lables", 642 "UserDefined", 643 0, }; 644 645 646LexerModule lmNsis(SCLEX_NSIS, ColouriseNsisDoc, "nsis", FoldNsisDoc, nsisWordLists); 647 648