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