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