1 // Scintilla source code edit control
2 /** @file LexTCL.cxx
3  ** Lexer for TCL language.
4  **/
5 // Copyright 1998-2001 by Andre Arpin <arpin@kingston.net>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include <stdarg.h>
12 #include <stdio.h>
13 
14 #include "Platform.h"
15 
16 #include "PropSet.h"
17 #include "Accessor.h"
18 #include "StyleContext.h"
19 #include "KeyWords.h"
20 #include "Scintilla.h"
21 #include "SciLexer.h"
22 
23 // Extended to accept accented characters
IsAWordChar(int ch)24 static inline bool IsAWordChar(int ch) {
25 	return ch >= 0x80 ||
26         (isalnum(ch) || ch == '_' || ch ==':' || ch=='.'); // : name space separator
27 }
28 
IsAWordStart(int ch)29 static inline bool IsAWordStart(int ch) {
30 	return ch >= 0x80 || (ch ==':' || isalpha(ch) || ch == '_');
31 }
32 
IsANumberChar(int ch)33 static inline bool IsANumberChar(int ch) {
34 	// Not exactly following number definition (several dots are seen as OK, etc.)
35 	// but probably enough in most cases.
36 	return (ch < 0x80) &&
37 	       (IsADigit(ch, 0x10) || toupper(ch) == 'E' ||
38 	        ch == '.' || ch == '-' || ch == '+');
39 }
40 
ColouriseTCLDoc(unsigned int startPos,int length,int,WordList * keywordlists[],Accessor & styler)41 static void ColouriseTCLDoc(unsigned int startPos, int length, int , WordList *keywordlists[], Accessor &styler) {
42 #define  isComment(s) (s==SCE_TCL_COMMENT || s==SCE_TCL_COMMENTLINE || s==SCE_TCL_COMMENT_BOX || s==SCE_TCL_BLOCK_COMMENT)
43 	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
44 	bool commentLevel = false;
45     bool subBrace = false; // substitution begin with a brace ${.....}
46 	enum tLineState {LS_DEFAULT, LS_OPEN_COMMENT, LS_OPEN_DOUBLE_QUOTE, LS_COMMENT_BOX, LS_MASK_STATE = 0xf,
47         LS_COMMAND_EXPECTED = 16, LS_BRACE_ONLY = 32 } lineState = LS_DEFAULT;
48 	bool prevSlash = false;
49 	int currentLevel = 0;
50     bool expected = 0;
51     bool subParen = 0;
52 
53 	int currentLine = styler.GetLine(startPos);
54     if (currentLine > 0)
55         currentLine--;
56 	length += startPos - styler.LineStart(currentLine);
57 	// make sure lines overlap
58 	startPos = styler.LineStart(currentLine);
59 
60 	WordList &keywords = *keywordlists[0];
61 	WordList &keywords2 = *keywordlists[1];
62 	WordList &keywords3 = *keywordlists[2];
63 	WordList &keywords4 = *keywordlists[3];
64 	WordList &keywords5 = *keywordlists[4];
65 	WordList &keywords6 = *keywordlists[5];
66 	WordList &keywords7 = *keywordlists[6];
67     WordList &keywords8 = *keywordlists[7];
68     WordList &keywords9 = *keywordlists[8];
69 
70 	if (currentLine > 0) {
71         int ls = styler.GetLineState(currentLine - 1);
72 		lineState = tLineState(ls & LS_MASK_STATE);
73 		expected = LS_COMMAND_EXPECTED == tLineState(ls & LS_COMMAND_EXPECTED);
74         subBrace = LS_BRACE_ONLY == tLineState(ls & LS_BRACE_ONLY);
75 		currentLevel = styler.LevelAt(currentLine - 1) >> 17;
76 		commentLevel = (styler.LevelAt(currentLine - 1) >> 16) & 1;
77 	} else
78 		styler.SetLevel(0, SC_FOLDLEVELBASE | SC_FOLDLEVELHEADERFLAG);
79 	bool visibleChars = false;
80 
81 	int previousLevel = currentLevel;
82     StyleContext sc(startPos, length, SCE_TCL_DEFAULT, styler);
83 	for (; ; sc.Forward()) {
84 next:
85         if (sc.ch=='\r' && sc.chNext == '\n') // only ignore \r on PC process on the mac
86             continue;
87         bool atEnd = !sc.More();  // make sure we coloured the last word
88         if (lineState != LS_DEFAULT) {
89             sc.SetState(SCE_TCL_DEFAULT);
90             if (lineState == LS_OPEN_COMMENT)
91                 sc.SetState(SCE_TCL_COMMENTLINE);
92             else if (lineState == LS_OPEN_DOUBLE_QUOTE)
93                 sc.SetState(SCE_TCL_IN_QUOTE);
94             else if (lineState == LS_COMMENT_BOX && (sc.ch == '#' || (sc.ch == ' ' && sc.chNext=='#')))
95                 sc.SetState(SCE_TCL_COMMENT_BOX);
96             lineState = LS_DEFAULT;
97         }
98         if (subBrace) { // ${ overrides every thing even \ except }
99             if (sc.ch == '}') {
100                 subBrace = false;
101                 sc.SetState(SCE_TCL_OPERATOR);
102                 sc.ForwardSetState(SCE_TCL_DEFAULT);
103                 goto next;
104             }
105             else
106                 sc.SetState(SCE_TCL_SUB_BRACE);
107             if (!sc.atLineEnd)
108                 continue;
109         } else if (sc.state == SCE_TCL_DEFAULT || sc.state ==SCE_TCL_OPERATOR) {
110             expected &= isspacechar(static_cast<unsigned char>(sc.ch)) || IsAWordStart(sc.ch) || sc.ch =='#';
111         } else if (sc.state == SCE_TCL_SUBSTITUTION) {
112             switch(sc.ch) {
113             case '(':
114                 subParen=true;
115                 sc.SetState(SCE_TCL_OPERATOR);
116                 sc.ForwardSetState(SCE_TCL_SUBSTITUTION);
117                 continue;
118             case ')':
119                 sc.SetState(SCE_TCL_OPERATOR);
120                 subParen=false;
121                 continue;
122             case '$':
123                 continue;
124             case ',':
125                 sc.SetState(SCE_TCL_OPERATOR);
126                 if (subParen)
127                     sc.ForwardSetState(SCE_TCL_SUBSTITUTION);
128                 continue;
129             default :
130                 // maybe spaces should be allowed ???
131                 if (!IsAWordChar(sc.ch)) { // probably the code is wrong
132                     sc.SetState(SCE_TCL_DEFAULT);
133                     subParen = 0;
134                 }
135                 break;
136             }
137         } else if (isComment(sc.state)) {
138         } else if (!IsAWordChar(sc.ch)) {
139             if ((sc.state == SCE_TCL_IDENTIFIER && expected) ||  sc.state == SCE_TCL_MODIFIER) {
140                 char w[100];
141                 char *s=w;
142                 sc.GetCurrent(w, sizeof(w));
143                 if (w[strlen(w)-1]=='\r')
144                     w[strlen(w)-1]=0;
145                 while(*s == ':') // ignore leading : like in ::set a 10
146                     ++s;
147                 bool quote = sc.state == SCE_TCL_IN_QUOTE;
148                 if (commentLevel  || expected) {
149                     if (keywords.InList(s)) {
150                         sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD);
151                     } else if (keywords2.InList(s)) {
152                         sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD2);
153                     } else if (keywords3.InList(s)) {
154                         sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD3);
155                     } else if (keywords4.InList(s)) {
156                         sc.ChangeState(quote ? SCE_TCL_WORD_IN_QUOTE : SCE_TCL_WORD4);
157                     } else if (sc.GetRelative(-static_cast<int>(strlen(s))-1) == '{' &&
158                         keywords5.InList(s) && sc.ch == '}') { // {keyword} exactly no spaces
159                             sc.ChangeState(SCE_TCL_EXPAND);
160                     }
161                     if (keywords6.InList(s)) {
162                         sc.ChangeState(SCE_TCL_WORD5);
163                     } else if (keywords7.InList(s)) {
164                         sc.ChangeState(SCE_TCL_WORD6);
165                     } else if (keywords8.InList(s)) {
166                         sc.ChangeState(SCE_TCL_WORD7);
167                     } else if (keywords9.InList(s)) {
168                         sc.ChangeState(SCE_TCL_WORD8);
169                     }
170                 }
171                 expected = false;
172                 sc.SetState(quote ? SCE_TCL_IN_QUOTE : SCE_TCL_DEFAULT);
173             } else if (sc.state == SCE_TCL_MODIFIER || sc.state == SCE_TCL_IDENTIFIER) {
174                 sc.SetState(SCE_TCL_DEFAULT);
175             }
176         }
177 		if (atEnd)
178 			break;
179         if (sc.atLineEnd) {
180             lineState = LS_DEFAULT;
181 			currentLine = styler.GetLine(sc.currentPos);
182 			if (foldComment && sc.state!=SCE_TCL_COMMENT && isComment(sc.state)) {
183 				if (currentLevel == 0) {
184 					++currentLevel;
185 					commentLevel = true;
186 				}
187 			} else {
188 				if (visibleChars && commentLevel) {
189 					--currentLevel;
190 					--previousLevel;
191 					commentLevel = false;
192 				}
193 			}
194 			int flag = 0;
195 			if (!visibleChars)
196 				flag = SC_FOLDLEVELWHITEFLAG;
197 			if (currentLevel > previousLevel)
198 				flag = SC_FOLDLEVELHEADERFLAG;
199 			styler.SetLevel(currentLine, flag + previousLevel + SC_FOLDLEVELBASE + (currentLevel << 17) + (commentLevel << 16));
200 
201 			// Update the line state, so it can be seen by next line
202 			if (sc.state == SCE_TCL_IN_QUOTE)
203 				lineState = LS_OPEN_DOUBLE_QUOTE;
204 			else {
205 			     if (prevSlash) {
206 				    if (isComment(sc.state))
207 					    lineState = LS_OPEN_COMMENT;
208                 } else if (sc.state == SCE_TCL_COMMENT_BOX)
209                     lineState = LS_COMMENT_BOX;
210 			}
211             styler.SetLineState(currentLine,
212                 (subBrace ? LS_BRACE_ONLY : 0) |
213                 (expected ? LS_COMMAND_EXPECTED : 0)  | lineState);
214             if (lineState == LS_COMMENT_BOX)
215                 sc.ForwardSetState(SCE_TCL_COMMENT_BOX);
216             else if (lineState == LS_OPEN_DOUBLE_QUOTE)
217                 sc.ForwardSetState(SCE_TCL_IN_QUOTE);
218             else
219                 sc.ForwardSetState(SCE_TCL_DEFAULT);
220 			prevSlash = false;
221 			previousLevel = currentLevel;
222 			goto next;
223 		}
224 
225 		if (prevSlash) {
226             prevSlash = false;
227             if (sc.ch == '#' && IsANumberChar(sc.chNext))
228                 sc.ForwardSetState(SCE_TCL_NUMBER);
229             continue;
230 		}
231         prevSlash = sc.ch == '\\';
232         if (isComment(sc.state))
233             continue;
234 		if (sc.atLineStart) {
235 			visibleChars = false;
236 			if (sc.state!=SCE_TCL_IN_QUOTE && !isComment(sc.state))
237             {
238 				sc.SetState(SCE_TCL_DEFAULT);
239                 expected = IsAWordStart(sc.ch)|| isspacechar(static_cast<unsigned char>(sc.ch));
240             }
241 		}
242 
243 		switch (sc.state) {
244 		case SCE_TCL_NUMBER:
245 			if (!IsANumberChar(sc.ch))
246 				sc.SetState(SCE_TCL_DEFAULT);
247 			break;
248 		case SCE_TCL_IN_QUOTE:
249 			if (sc.ch == '"') {
250 				sc.ForwardSetState(SCE_TCL_DEFAULT);
251 				visibleChars = true; // necessary if a " is the first and only character on a line
252 				goto next;
253 			} else if (sc.ch == '[' || sc.ch == ']' || sc.ch == '$') {
254 				sc.SetState(SCE_TCL_OPERATOR);
255                 expected = sc.ch == '[';
256                 sc.ForwardSetState(SCE_TCL_IN_QUOTE);
257 				goto next;
258 			}
259             continue;
260         case SCE_TCL_OPERATOR:
261 			sc.SetState(SCE_TCL_DEFAULT);
262 			break;
263 		}
264 
265 		if (sc.ch == '#') {
266 			if (visibleChars) {
267                 if (sc.state != SCE_TCL_IN_QUOTE && expected)
268 					sc.SetState(SCE_TCL_COMMENT);
269 			} else {
270                 sc.SetState(SCE_TCL_COMMENTLINE);
271                 if (sc.chNext == '~')
272                     sc.SetState(SCE_TCL_BLOCK_COMMENT);
273                 if (sc.atLineStart && (sc.chNext == '#' || sc.chNext == '-'))
274                         sc.SetState(SCE_TCL_COMMENT_BOX);
275             }
276         }
277 
278 		if (!isspacechar(static_cast<unsigned char>(sc.ch))) {
279 			visibleChars = true;
280 		}
281 
282 		if (sc.ch == '\\') {
283 			prevSlash = true;
284 			continue;
285 		}
286 
287 		// Determine if a new state should be entered.
288 		if (sc.state == SCE_TCL_DEFAULT) {
289             if (IsAWordStart(sc.ch)) {
290 				sc.SetState(SCE_TCL_IDENTIFIER);
291 			} else if (IsADigit(sc.ch) && !IsAWordChar(sc.chPrev)) {
292 				sc.SetState(SCE_TCL_NUMBER);
293 			} else {
294 				switch (sc.ch) {
295 				case '\"':
296 					sc.SetState(SCE_TCL_IN_QUOTE);
297 					break;
298 				case '{':
299 					sc.SetState(SCE_TCL_OPERATOR);
300 					expected = true;
301 					++currentLevel;
302 					break;
303 				case '}':
304 					sc.SetState(SCE_TCL_OPERATOR);
305 					--currentLevel;
306 					break;
307 				case '[':
308                     expected = true;
309 				case ']':
310 				case '(':
311 				case ')':
312 					sc.SetState(SCE_TCL_OPERATOR);
313 					break;
314 				case ';':
315                     expected = true;
316 					break;
317                 case '$':
318                     subParen = 0;
319                     if (sc.chNext != '{') {
320                         sc.SetState(SCE_TCL_SUBSTITUTION);
321                     }
322                     else {
323                         sc.SetState(SCE_TCL_OPERATOR);  // $
324                         sc.Forward();  // {
325                         sc.ForwardSetState(SCE_TCL_SUB_BRACE);
326                         subBrace = true;
327                     }
328                     break;
329                 case '#':
330                     if ((isspacechar(static_cast<unsigned char>(sc.chPrev))||
331                             isoperator(static_cast<char>(sc.chPrev))) && IsADigit(sc.chNext,0x10))
332                         sc.SetState(SCE_TCL_NUMBER);
333                     break;
334                 case '-':
335                     sc.SetState(IsADigit(sc.chNext)? SCE_TCL_NUMBER: SCE_TCL_MODIFIER);
336                     break;
337                 default:
338                     if (isoperator(static_cast<char>(sc.ch))) {
339                         sc.SetState(SCE_TCL_OPERATOR);
340                     }
341 				}
342 			}
343 		}
344 	}
345 	sc.Complete();
346 }
347 
348 static const char * const tclWordListDesc[] = {
349             "TCL Keywords",
350             "TK Keywords",
351             "iTCL Keywords",
352             "tkCommands",
353             "expand"
354             "user1",
355             "user2",
356             "user3",
357             "user4",
358             0
359         };
360 
361 // this code supports folding in the colourizer
362 LexerModule lmTCL(SCLEX_TCL, ColouriseTCLDoc, "tcl", 0, tclWordListDesc);
363