text: // Scintilla source code edit control
  /** @file LexTCL.cxx
   ** Lexer for TCL language.
   **/
  // Copyright 1998-2001 by Andre Arpin <arpin@kingston.net>
  // The License.txt file describes the conditions under which this software may be distributed.
  
  #include <stdlib.h>
  #include <string.h>
  #include <stdio.h>
  #include <stdarg.h>
  #include <assert.h>
  #include <ctype.h>
  
  #include "ILexer.h"
  #include "Scintilla.h"
  #include "SciLexer.h"
  
  #include "WordList.h"
  #include "LexAccessor.h"
  #include "Accessor.h"
  #include "StyleContext.h"
  #include "CharacterSet.h"
  #include "LexerModule.h"
  
  using namespace Scintilla;
  
  // Extended to accept accented characters
  static inline bool IsAWordChar(int ch) {
  	return ch >= 0x80 ||
  	       (isalnum(ch) || ch == '_' || ch ==':' || ch=='.'); // : name space separator
  }
  
  static inline bool IsAWordStart(int ch) {
  	return ch >= 0x80 || (ch ==':' || isalpha(ch) || ch == '_');
  }
  
  static inline bool IsANumberChar(int ch) {
  	// Not exactly following number definition (several dots are seen as OK, etc.)
  	// but probably enough in most cases.
  	return (ch < 0x80) &&
  	       (IsADigit(ch, 0x10) || toupper(ch) == 'E' ||
  	        ch == '.' || ch == '-' || ch == '+');
  }
  
  static void ColouriseTCLDoc(Sci_PositionU startPos, Sci_Position length, int , WordList *keywordlists[], Accessor &styler) {
  #define  isComment(s) (s==SCE_TCL_COMMENT || s==SCE_TCL_COMMENTLINE || s==SCE_TCL_COMMENT_BOX || s==SCE_TCL_BLOCK_COMMENT)
  	const bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
  	const bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
  	bool commentLevel = false;
  	bool subBrace = false; // substitution begin with a brace ${.....}
  	enum tLineState {LS_DEFAULT, LS_OPEN_COMMENT, LS_OPEN_DOUBLE_QUOTE, LS_COMMENT_BOX, LS_MASK_STATE = 0xf,
  	                 LS_COMMAND_EXPECTED = 16, LS_BRACE_ONLY = 32
  	                } lineState = LS_DEFAULT;
  	bool prevSlash = false;
  	int currentLevel = 0;
  	bool expected = 0;
  	bool subParen = 0;
  
  	Sci_Position currentLine = styler.GetLine(startPos);
  	if (currentLine > 0)
  		currentLine--;
  	length += startPos - styler.LineStart(currentLine);
  	// make sure lines overlap
  	startPos = styler.LineStart(currentLine);
  
  	WordList &keywords = *keywordlists[0];
  	WordList &keywords2 = *keywordlists[1];
  	WordList &keywords3 = *keywordlists[2];
  	WordList &keywords4 = *keywordlists[3];
  	WordList &keywords5 = *keywordlists[4];
  	WordList &keywords6 = *keywordlists[5];
  	WordList &keywords7 = *keywordlists[6];
  	WordList &keywords8 = *keywordlists[7];
  	WordList &keywords9 = *keywordlists[8];
  
  	if (currentLine > 0) {
  		int ls = styler.GetLineState(currentLine - 1);
  		lineState = tLineState(ls & LS_MASK_STATE);
  		expected = LS_COMMAND_EXPECTED == tLineState(ls & LS_COMMAND_EXPECTED);
  		subBrace = LS_BRACE_ONLY == tLineState(ls & LS_BRACE_ONLY);
  		currentLevel = styler.LevelAt(currentLine - 1) >> 17;
  		commentLevel = (styler.LevelAt(currentLine - 1) >> 16) & 1;
  	} else
  		styler.SetLevel(0, SC_FOLDLEVELBASE | SC_FOLDLEVELHEADERFLAG);
  	bool visibleChars = false;
  
  	int previousLevel = currentLevel;
  	StyleContext sc(startPos, length, SCE_TCL_DEFAULT, styler);
  	for (; ; sc.Forward()) {
  next:
  		if (sc.ch=='\r' && sc.chNext == '\n') // only ignore \r on PC process on the mac
  			continue;
  		bool atEnd = !sc.More();  // make sure we coloured the last word
  		if (lineState != LS_DEFAULT) {
  			sc.SetState(SCE_TCL_DEFAULT);
  			if (lineState == LS_OPEN_COMMENT)
  				sc.SetState(SCE_TCL_COMMENTLINE);
  			else if (lineState == LS_OPEN_DOUBLE_QUOTE)
  				sc.SetState(SCE_TCL_IN_QUOTE);
  			else if (lineState == LS_COMMENT_BOX && (sc.ch == '#' || (sc.ch == ' ' && sc.chNext=='#')))
  				sc.SetState(SCE_TCL_COMMENT_BOX);
  			lineState = LS_DEFAULT;
  		}
  		if (subBrace) { // ${ overrides every thing even \ except }
  			if (sc.ch == '}') {
  				subBrace = false;
  				sc.SetState(SCE_TCL_OPERATOR);
  				sc.ForwardSetState(SCE_TCL_DEFAULT);
  				goto next;
  			} else
  				sc.SetState(SCE_TCL_SUB_BRACE);
  			if (!sc.atLineEnd)
  				continue;
  		} else if (sc.state == SCE_TCL_DEFAULT || sc.state ==SCE_TCL_OPERATOR) {
  			expected &= isspacechar(static_cast<unsigned char>(sc.ch)) || IsAWordStart(sc.ch) || sc.ch =='#';
  		} else if (sc.state == SCE_TCL_SUBSTITUTION) {
  			switch (sc.ch) {
  			case '(':
  				subParen=true;
  				sc.SetState(SCE_TCL_OPERATOR);
  				sc.ForwardSetState(SCE_TCL_SUBSTITUTION);
  				continue;
  			case ')':
  				sc.SetState(SCE_TCL_OPERATOR);
  				subParen=false;
  				continue;
  			case '$':
  				continue;
  			case ',':
  				sc.SetState(SCE_TCL_OPERATOR);
  				if (subParen) {
  					sc.ForwardSetState(SCE_TCL_SUBSTITUTION);
  					goto next;	// Already forwarded so avoid loop's Forward()
  				}
  				continue;
  			default :
  				// maybe spaces should be allowed ???
  				if (!IsAWordChar(sc.ch)) { // probably the code is wrong
  					sc.SetState(SCE_TCL_DEFAULT);
  					subParen = 0;
  				}
  				break;
  			}
  		} else if (isComment(sc.state)) {
  		} else if (!IsAWordChar(sc.ch)) {
  			if ((sc.state == SCE_TCL_IDENTIFIER && expected) ||  sc.state == SCE_TCL_MODIFIER) {
  				char w[100];
  				char *s=w;
  				sc.GetCurrent(w, sizeof(w));
  				if (w[strlen(w)-1]=='\r')
  					w[strlen(w)-1]=0;
  				while (*s == ':') // ignore leading : like in ::set a 10
  					++s;
  				bool quote = sc.state == SCE_TCL_IN_QUOTE;
  				if (commentLevel  || expected) {
  					if (keywords.InList(s)) {
  						sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD);
  					} else if (keywords2.InList(s)) {
  						sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD2);
  					} else if (keywords3.InList(s)) {
  						sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD3);
  					} else if (keywords4.InList(s)) {
  						sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD4);
  					} else if (sc.GetRelative(-static_cast<int>(strlen(s))-1) == '{' &&
  					           keywords5.InList(s) && sc.ch == '}') { // {keyword} exactly no spaces
  						sc.ChangeState(SCE_TCL_EXPAND);
  					}
  					if (keywords6.InList(s)) {
  						sc.ChangeState(SCE_TCL_WORD5);
  					} else if (keywords7.InList(s)) {
  						sc.ChangeState(SCE_TCL_WORD6);
  					} else if (keywords8.InList(s)) {
  						sc.ChangeState(SCE_TCL_WORD7);
  					} else if (keywords9.InList(s)) {
  						sc.ChangeState(SCE_TCL_WORD8);
  					}
  				}
  				expected = false;
  				sc.SetState(quote ? SCE_TCL_IN_QUOTE : SCE_TCL_DEFAULT);
  			} else if (sc.state == SCE_TCL_MODIFIER || sc.state == SCE_TCL_IDENTIFIER) {
  				sc.SetState(SCE_TCL_DEFAULT);
  			}
  		}
  		if (atEnd)
  			break;
  		if (sc.atLineEnd) {
  			lineState = LS_DEFAULT;
  			currentLine = styler.GetLine(sc.currentPos);
  			if (foldComment && sc.state!=SCE_TCL_COMMENT && isComment(sc.state)) {
  				if (currentLevel == 0) {
  					++currentLevel;
  					commentLevel = true;
  				}
  			} else {
  				if (visibleChars && commentLevel) {
  					--currentLevel;
  					--previousLevel;
  					commentLevel = false;
  				}
  			}
  			int flag = 0;
  			if (!visibleChars && foldCompact)
  				flag = SC_FOLDLEVELWHITEFLAG;
  			if (currentLevel > previousLevel)
  				flag = SC_FOLDLEVELHEADERFLAG;
  			styler.SetLevel(currentLine, flag + previousLevel + SC_FOLDLEVELBASE + (currentLevel << 17) + (commentLevel << 16));
  
  			// Update the line state, so it can be seen by next line
  			if (sc.state == SCE_TCL_IN_QUOTE) {
  				lineState = LS_OPEN_DOUBLE_QUOTE;
  			} else {
  				if (prevSlash) {
  					if (isComment(sc.state))
  						lineState = LS_OPEN_COMMENT;
  				} else if (sc.state == SCE_TCL_COMMENT_BOX)
  					lineState = LS_COMMENT_BOX;
  			}
  			styler.SetLineState(currentLine,
  			                    (subBrace ? LS_BRACE_ONLY : 0) |
  			                    (expected ? LS_COMMAND_EXPECTED : 0)  | lineState);
  			if (lineState == LS_COMMENT_BOX)
  				sc.ForwardSetState(SCE_TCL_COMMENT_BOX);
  			else if (lineState == LS_OPEN_DOUBLE_QUOTE)
  				sc.ForwardSetState(SCE_TCL_IN_QUOTE);
  			else
  				sc.ForwardSetState(SCE_TCL_DEFAULT);
  			prevSlash = false;
  			previousLevel = currentLevel;
  			goto next;
  		}
  
  		if (prevSlash) {
  			prevSlash = false;
  			if (sc.ch == '#' && IsANumberChar(sc.chNext))
  				sc.ForwardSetState(SCE_TCL_NUMBER);
  			continue;
  		}
  		prevSlash = sc.ch == '\\';
  		if (isComment(sc.state))
  			continue;
  		if (sc.atLineStart) {
  			visibleChars = false;
  			if (sc.state!=SCE_TCL_IN_QUOTE && !isComment(sc.state))
  			{
  				sc.SetState(SCE_TCL_DEFAULT);
  				expected = IsAWordStart(sc.ch)|| isspacechar(static_cast<unsigned char>(sc.ch));
  			}
  		}
  
  		switch (sc.state) {
  		case SCE_TCL_NUMBER:
  			if (!IsANumberChar(sc.ch))
  				sc.SetState(SCE_TCL_DEFAULT);
  			break;
  		case SCE_TCL_IN_QUOTE:
  			if (sc.ch == '"') {
  				sc.ForwardSetState(SCE_TCL_DEFAULT);
  				visibleChars = true; // necessary if a " is the first and only character on a line
  				goto next;
  			} else if (sc.ch == '[' || sc.ch == ']' || sc.ch == '$') {
  				sc.SetState(SCE_TCL_OPERATOR);
  				expected = sc.ch == '[';
  				sc.ForwardSetState(SCE_TCL_IN_QUOTE);
  				goto next;
  			}
  			continue;
  		case SCE_TCL_OPERATOR:
  			sc.SetState(SCE_TCL_DEFAULT);
  			break;
  		}
  
  		if (sc.ch == '#') {
  			if (visibleChars) {
  				if (sc.state != SCE_TCL_IN_QUOTE && expected)
  					sc.SetState(SCE_TCL_COMMENT);
  			} else {
  				sc.SetState(SCE_TCL_COMMENTLINE);
  				if (sc.chNext == '~')
  					sc.SetState(SCE_TCL_BLOCK_COMMENT);
  				if (sc.atLineStart && (sc.chNext == '#' || sc.chNext == '-'))
  					sc.SetState(SCE_TCL_COMMENT_BOX);
  			}
  		}
  
  		if (!isspacechar(static_cast<unsigned char>(sc.ch))) {
  			visibleChars = true;
  		}
  
  		if (sc.ch == '\\') {
  			prevSlash = true;
  			continue;
  		}
  
  		// Determine if a new state should be entered.
  		if (sc.state == SCE_TCL_DEFAULT) {
  			if (IsAWordStart(sc.ch)) {
  				sc.SetState(SCE_TCL_IDENTIFIER);
  			} else if (IsADigit(sc.ch) && !IsAWordChar(sc.chPrev)) {
  				sc.SetState(SCE_TCL_NUMBER);
  			} else {
  				switch (sc.ch) {
  				case '\"':
  					sc.SetState(SCE_TCL_IN_QUOTE);
  					break;
  				case '{':
  					sc.SetState(SCE_TCL_OPERATOR);
  					expected = true;
  					++currentLevel;
  					break;
  				case '}':
  					sc.SetState(SCE_TCL_OPERATOR);
  					expected = true;
  					--currentLevel;
  					break;
  				case '[':
  					expected = true;
  					// Falls through.
  				case ']':
  				case '(':
  				case ')':
  					sc.SetState(SCE_TCL_OPERATOR);
  					break;
  				case ';':
  					expected = true;
  					break;
  				case '$':
  					subParen = 0;
  					if (sc.chNext != '{') {
  						sc.SetState(SCE_TCL_SUBSTITUTION);
  					} else {
  						sc.SetState(SCE_TCL_OPERATOR);  // $
  						sc.Forward();  // {
  						sc.ForwardSetState(SCE_TCL_SUB_BRACE);
  						subBrace = true;
  					}
  					break;
  				case '#':
  					if ((isspacechar(static_cast<unsigned char>(sc.chPrev))||
  					        isoperator(static_cast<char>(sc.chPrev))) && IsADigit(sc.chNext,0x10))
  						sc.SetState(SCE_TCL_NUMBER);
  					break;
  				case '-':
  					sc.SetState(IsADigit(sc.chNext)? SCE_TCL_NUMBER: SCE_TCL_MODIFIER);
  					break;
  				default:
  					if (isoperator(static_cast<char>(sc.ch))) {
  						sc.SetState(SCE_TCL_OPERATOR);
  					}
  				}
  			}
  		}
  	}
  	sc.Complete();
  }
  
  static const char *const tclWordListDesc[] = {
  	"TCL Keywords",
  	"TK Keywords",
  	"iTCL Keywords",
  	"tkCommands",
  	"expand",
  	"user1",
  	"user2",
  	"user3",
  	"user4",
  	0
  };
  
  // this code supports folding in the colourizer
  LexerModule lmTCL(SCLEX_TCL, ColouriseTCLDoc, "tcl", 0, tclWordListDesc);
  