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