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