]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/lexers/LexTCMD.cxx
Initial copy of Scintilla 3.21 code
[wxWidgets.git] / src / stc / scintilla / lexers / LexTCMD.cxx
1 // Scintilla\ source code edit control
2 /** @file LexTCMD.cxx
3 ** Lexer for Take Command / TCC batch scripts (.bat, .btm, .cmd).
4 **/
5 // Written by Rex Conn (rconn [at] jpsoft [dot] com)
6 // based on the CMD lexer
7 // The License.txt file describes the conditions under which this software may be distributed.
8
9 #include <stdlib.h>
10 #include <string.h>
11 #include <stdio.h>
12 #include <stdarg.h>
13 #include <assert.h>
14 #include <ctype.h>
15
16 #include "ILexer.h"
17 #include "Scintilla.h"
18 #include "SciLexer.h"
19
20 #include "WordList.h"
21 #include "LexAccessor.h"
22 #include "Accessor.h"
23 #include "StyleContext.h"
24 #include "CharacterSet.h"
25 #include "LexerModule.h"
26
27 #ifdef SCI_NAMESPACE
28 using namespace Scintilla;
29 #endif
30
31
32 static bool IsAlphabetic(int ch) {
33 return isascii(ch) && isalpha(ch);
34 }
35
36 static inline bool AtEOL(Accessor &styler, unsigned int i) {
37 return (styler[i] == '\n') || ((styler[i] == '\r') && (styler.SafeGetCharAt(i + 1) != '\n'));
38 }
39
40 // Tests for BATCH Operators
41 static bool IsBOperator(char ch) {
42 return (ch == '=') || (ch == '+') || (ch == '>') || (ch == '<') || (ch == '|') || (ch == '&') || (ch == '!') || (ch == '?') || (ch == '*') || (ch == '(') || (ch == ')');
43 }
44
45 // Tests for BATCH Separators
46 static bool IsBSeparator(char ch) {
47 return (ch == '\\') || (ch == '.') || (ch == ';') || (ch == ' ') || (ch == '\t') || (ch == '[') || (ch == ']') || (ch == '\"') || (ch == '\'') || (ch == '/');
48 }
49
50 // Tests for Environment Variable symbol
51 static inline bool IsEnvironmentVar(char ch) {
52 return isalpha(ch) || isdigit(ch) || (ch == '_') || (ch == '$');
53 }
54
55 // Find length of CMD FOR variable with modifier (%~...) or return 0
56 static unsigned int GetBatchVarLen( char *wordBuffer )
57 {
58 int nLength = 0;
59 if ( wordBuffer[0] == '%' ) {
60
61 if ( wordBuffer[1] == '~' )
62 nLength = 2;
63 else if (( wordBuffer[1] == '%' ) && ( wordBuffer[2] == '~' ))
64 nLength++;
65 else
66 return 0;
67
68 for ( ; ( wordBuffer[nLength] ); nLength++ ) {
69
70 switch ( toupper(wordBuffer[nLength]) ) {
71 case 'A':
72 // file attributes
73 case 'D':
74 // drive letter only
75 case 'F':
76 // fully qualified path name
77 case 'N':
78 // filename only
79 case 'P':
80 // path only
81 case 'S':
82 // short name
83 case 'T':
84 // date / time of file
85 case 'X':
86 // file extension only
87 case 'Z':
88 // file size
89 break;
90 default:
91 return nLength;
92 }
93 }
94 }
95
96 return nLength;
97 }
98
99
100 static void ColouriseTCMDLine( char *lineBuffer, unsigned int lengthLine, unsigned int startLine, unsigned int endPos, WordList *keywordlists[], Accessor &styler)
101 {
102 unsigned int offset = 0; // Line Buffer Offset
103 char wordBuffer[260]; // Word Buffer - large to catch long paths
104 unsigned int wbl; // Word Buffer Length
105 unsigned int wbo; // Word Buffer Offset - also Special Keyword Buffer Length
106 WordList &keywords = *keywordlists[0]; // Internal Commands
107 // WordList &keywords2 = *keywordlists[1]; // Aliases (optional)
108 bool isDelayedExpansion = 1; // !var!
109
110 bool continueProcessing = true; // Used to toggle Regular Keyword Checking
111 // Special Keywords are those that allow certain characters without whitespace after the command
112 // Examples are: cd. cd\ echo: echo. path=
113 bool inString = false; // Used for processing while ""
114 // Special Keyword Buffer used to determine if the first n characters is a Keyword
115 char sKeywordBuffer[260]; // Special Keyword Buffer
116 bool sKeywordFound; // Exit Special Keyword for-loop if found
117
118 // Skip leading whitespace
119 while ((offset < lengthLine) && (isspacechar(lineBuffer[offset]))) {
120 offset++;
121 }
122 // Colorize Default Text
123 styler.ColourTo(startLine + offset - 1, SCE_TCMD_DEFAULT);
124
125 if ( offset >= lengthLine )
126 return;
127
128 // Check for Fake Label (Comment) or Real Label - return if found
129 if (lineBuffer[offset] == ':') {
130 if (lineBuffer[offset + 1] == ':') {
131 // Colorize Fake Label (Comment) - :: is the same as REM
132 styler.ColourTo(endPos, SCE_TCMD_COMMENT);
133 } else {
134 // Colorize Real Label
135 styler.ColourTo(endPos, SCE_TCMD_LABEL);
136 }
137 return;
138
139 // Check for Comment - return if found
140 } else if (( CompareNCaseInsensitive(lineBuffer+offset, "rem", 3) == 0 ) && (( lineBuffer[offset+3] == 0 ) || ( isspace(lineBuffer[offset+3] )))) {
141 styler.ColourTo(endPos, SCE_TCMD_COMMENT);
142 return;
143
144 // Check for Drive Change (Drive Change is internal command) - return if found
145 } else if ((IsAlphabetic(lineBuffer[offset])) &&
146 (lineBuffer[offset + 1] == ':') &&
147 ((isspacechar(lineBuffer[offset + 2])) ||
148 (((lineBuffer[offset + 2] == '\\')) &&
149 (isspacechar(lineBuffer[offset + 3]))))) {
150 // Colorize Regular Keyword
151 styler.ColourTo(endPos, SCE_TCMD_WORD);
152 return;
153 }
154
155 // Check for Hide Command (@ECHO OFF/ON)
156 if (lineBuffer[offset] == '@') {
157 styler.ColourTo(startLine + offset, SCE_TCMD_HIDE);
158 offset++;
159 }
160 // Skip whitespace
161 while ((offset < lengthLine) && (isspacechar(lineBuffer[offset]))) {
162 offset++;
163 }
164
165 // Read remainder of line word-at-a-time or remainder-of-word-at-a-time
166 while (offset < lengthLine) {
167 if (offset > startLine) {
168 // Colorize Default Text
169 styler.ColourTo(startLine + offset - 1, SCE_TCMD_DEFAULT);
170 }
171 // Copy word from Line Buffer into Word Buffer
172 wbl = 0;
173 for (; offset < lengthLine && ( wbl < 260 ) && !isspacechar(lineBuffer[offset]); wbl++, offset++) {
174 wordBuffer[wbl] = static_cast<char>(tolower(lineBuffer[offset]));
175 }
176 wordBuffer[wbl] = '\0';
177 wbo = 0;
178
179 // Check for Separator
180 if (IsBSeparator(wordBuffer[0])) {
181
182 // Reset Offset to re-process remainder of word
183 offset -= (wbl - 1);
184 // Colorize Default Text
185 styler.ColourTo(startLine + offset - 1, SCE_BAT_DEFAULT);
186
187 if (wordBuffer[0] == '"')
188 inString = !inString;
189
190 // Check for Regular expression
191 } else if (( wordBuffer[0] == ':' ) && ( wordBuffer[1] == ':' ) && (continueProcessing)) {
192
193 // Colorize Regular exoressuin
194 styler.ColourTo(startLine + offset - 1, SCE_TCMD_DEFAULT);
195 // No need to Reset Offset
196
197 // Check for Labels in text (... :label)
198 } else if (wordBuffer[0] == ':' && isspacechar(lineBuffer[offset - wbl - 1])) {
199 // Colorize Default Text
200 styler.ColourTo(startLine + offset - 1 - wbl, SCE_TCMD_DEFAULT);
201 // Colorize Label
202 styler.ColourTo(startLine + offset - 1, SCE_TCMD_CLABEL);
203 // No need to Reset Offset
204 // Check for delayed expansion Variable (!x...!)
205 } else if (isDelayedExpansion && wordBuffer[0] == '!') {
206 // Colorize Default Text
207 styler.ColourTo(startLine + offset - 1 - wbl, SCE_TCMD_DEFAULT);
208 wbo++;
209 // Search to end of word for second !
210 while ((wbo < wbl) && (wordBuffer[wbo] != '!') && (!IsBOperator(wordBuffer[wbo])) && (!IsBSeparator(wordBuffer[wbo]))) {
211 wbo++;
212 }
213 if (wordBuffer[wbo] == '!') {
214 wbo++;
215 // Colorize Environment Variable
216 styler.ColourTo(startLine + offset - 1 - (wbl - wbo), SCE_TCMD_EXPANSION);
217 } else {
218 wbo = 1;
219 // Colorize Symbol
220 styler.ColourTo(startLine + offset - 1 - (wbl - 1), SCE_TCMD_DEFAULT);
221 }
222
223 // Reset Offset to re-process remainder of word
224 offset -= (wbl - wbo);
225
226 // Check for Regular Keyword in list
227 } else if ((keywords.InList(wordBuffer)) && (!inString) && (continueProcessing)) {
228
229 // ECHO, PATH, and PROMPT require no further Regular Keyword Checking
230 if ((CompareCaseInsensitive(wordBuffer, "echo") == 0) ||
231 (CompareCaseInsensitive(sKeywordBuffer, "echos") == 0) ||
232 (CompareCaseInsensitive(sKeywordBuffer, "echoerr") == 0) ||
233 (CompareCaseInsensitive(sKeywordBuffer, "echoserr") == 0) ||
234 (CompareCaseInsensitive(wordBuffer, "path") == 0) ||
235 (CompareCaseInsensitive(wordBuffer, "prompt") == 0)) {
236 continueProcessing = false;
237 }
238
239 // Colorize Regular keyword
240 styler.ColourTo(startLine + offset - 1, SCE_TCMD_WORD);
241 // No need to Reset Offset
242
243 } else if ((wordBuffer[0] != '%') && (wordBuffer[0] != '!') && (!IsBOperator(wordBuffer[0])) && (!inString) && (continueProcessing)) {
244
245 // a few commands accept "illegal" syntax -- cd\, echo., etc.
246 sscanf( wordBuffer, "%[^.<>|&=\\/]", sKeywordBuffer );
247 sKeywordFound = false;
248
249 if ((CompareCaseInsensitive(sKeywordBuffer, "echo") == 0) ||
250 (CompareCaseInsensitive(sKeywordBuffer, "echos") == 0) ||
251 (CompareCaseInsensitive(sKeywordBuffer, "echoerr") == 0) ||
252 (CompareCaseInsensitive(sKeywordBuffer, "echoserr") == 0) ||
253 (CompareCaseInsensitive(sKeywordBuffer, "cd") == 0) ||
254 (CompareCaseInsensitive(sKeywordBuffer, "path") == 0) ||
255 (CompareCaseInsensitive(sKeywordBuffer, "prompt") == 0)) {
256
257 // no further Regular Keyword Checking
258 continueProcessing = false;
259 sKeywordFound = true;
260 wbo = (unsigned int)strlen( sKeywordBuffer );
261
262 // Colorize Special Keyword as Regular Keyword
263 styler.ColourTo(startLine + offset - 1 - (wbl - wbo), SCE_TCMD_WORD);
264 // Reset Offset to re-process remainder of word
265 offset -= (wbl - wbo);
266 }
267
268 // Check for Default Text
269 if (!sKeywordFound) {
270 wbo = 0;
271 // Read up to %, Operator or Separator
272 while ((wbo < wbl) && (wordBuffer[wbo] != '%') && (!isDelayedExpansion || wordBuffer[wbo] != '!') && (!IsBOperator(wordBuffer[wbo])) && (!IsBSeparator(wordBuffer[wbo]))) {
273 wbo++;
274 }
275 // Colorize Default Text
276 styler.ColourTo(startLine + offset - 1 - (wbl - wbo), SCE_TCMD_DEFAULT);
277 // Reset Offset to re-process remainder of word
278 offset -= (wbl - wbo);
279 }
280
281 // Check for Argument (%n), Environment Variable (%x...%) or Local Variable (%%a)
282 } else if (wordBuffer[0] == '%') {
283 unsigned int varlen;
284 unsigned int n = 1;
285 // Colorize Default Text
286 styler.ColourTo(startLine + offset - 1 - wbl, SCE_TCMD_DEFAULT);
287 wbo++;
288
289 // check for %[nn] syntax
290 if ( wordBuffer[1] == '[' ) {
291 n++;
292 while ((n < wbl) && (wordBuffer[n] != ']')) {
293 n++;
294 }
295 if ( wordBuffer[n] == ']' )
296 n++;
297 goto ColorizeArg;
298 }
299
300 // Search to end of word for second % or to the first terminator (can be a long path)
301 while ((wbo < wbl) && (wordBuffer[wbo] != '%') && (!IsBOperator(wordBuffer[wbo])) && (!IsBSeparator(wordBuffer[wbo]))) {
302 wbo++;
303 }
304
305 // Check for Argument (%n) or (%*)
306 if (((isdigit(wordBuffer[1])) || (wordBuffer[1] == '*')) && (wordBuffer[wbo] != '%')) {
307 while (( wordBuffer[n] ) && ( strchr( "%0123456789*#$", wordBuffer[n] ) != NULL ))
308 n++;
309 ColorizeArg:
310 // Colorize Argument
311 styler.ColourTo(startLine + offset - 1 - (wbl - n), SCE_TCMD_IDENTIFIER);
312 // Reset Offset to re-process remainder of word
313 offset -= (wbl - n);
314
315 // Check for Variable with modifiers (%~...)
316 } else if ((varlen = GetBatchVarLen(wordBuffer)) != 0) {
317
318 // Colorize Variable
319 styler.ColourTo(startLine + offset - 1 - (wbl - varlen), SCE_TCMD_IDENTIFIER);
320 // Reset Offset to re-process remainder of word
321 offset -= (wbl - varlen);
322
323 // Check for Environment Variable (%x...%)
324 } else if (( wordBuffer[1] ) && ( wordBuffer[1] != '%')) {
325 if ( wordBuffer[wbo] == '%' )
326 wbo++;
327
328 // Colorize Environment Variable
329 styler.ColourTo(startLine + offset - 1 - (wbl - wbo), SCE_TCMD_ENVIRONMENT);
330 // Reset Offset to re-process remainder of word
331 offset -= (wbl - wbo);
332
333 // Check for Local Variable (%%a)
334 } else if ( (wbl > 2) && (wordBuffer[1] == '%') && (wordBuffer[2] != '%') && (!IsBOperator(wordBuffer[2])) && (!IsBSeparator(wordBuffer[2]))) {
335
336 n = 2;
337 while (( wordBuffer[n] ) && (!IsBOperator(wordBuffer[n])) && (!IsBSeparator(wordBuffer[n])))
338 n++;
339
340 // Colorize Local Variable
341 styler.ColourTo(startLine + offset - 1 - (wbl - n), SCE_TCMD_IDENTIFIER);
342 // Reset Offset to re-process remainder of word
343 offset -= (wbl - n);
344
345 // Check for %%
346 } else if ((wbl > 1) && (wordBuffer[1] == '%')) {
347
348 // Colorize Symbols
349 styler.ColourTo(startLine + offset - 1 - (wbl - 2), SCE_TCMD_DEFAULT);
350 // Reset Offset to re-process remainder of word
351 offset -= (wbl - 2);
352 } else {
353
354 // Colorize Symbol
355 styler.ColourTo(startLine + offset - 1 - (wbl - 1), SCE_TCMD_DEFAULT);
356 // Reset Offset to re-process remainder of word
357 offset -= (wbl - 1);
358 }
359
360 // Check for Operator
361 } else if (IsBOperator(wordBuffer[0])) {
362 // Colorize Default Text
363 styler.ColourTo(startLine + offset - 1 - wbl, SCE_TCMD_DEFAULT);
364
365 // Check for Pipe, compound, or conditional Operator
366 if ((wordBuffer[0] == '|') || (wordBuffer[0] == '&')) {
367
368 // Colorize Pipe Operator
369 styler.ColourTo(startLine + offset - 1 - (wbl - 1), SCE_TCMD_OPERATOR);
370 // Reset Offset to re-process remainder of word
371 offset -= (wbl - 1);
372 continueProcessing = true;
373
374 // Check for Other Operator
375 } else {
376 // Check for > Operator
377 if ((wordBuffer[0] == '>') || (wordBuffer[0] == '<')) {
378 // Turn Keyword and External Command / Program checking back on
379 continueProcessing = true;
380 }
381 // Colorize Other Operator
382 if (!inString || !(wordBuffer[0] == '(' || wordBuffer[0] == ')'))
383 styler.ColourTo(startLine + offset - 1 - (wbl - 1), SCE_TCMD_OPERATOR);
384 // Reset Offset to re-process remainder of word
385 offset -= (wbl - 1);
386 }
387
388 // Check for Default Text
389 } else {
390 // Read up to %, Operator or Separator
391 while ((wbo < wbl) && (wordBuffer[wbo] != '%') && (!isDelayedExpansion || wordBuffer[wbo] != '!') && (!IsBOperator(wordBuffer[wbo])) && (!IsBSeparator(wordBuffer[wbo]))) {
392 wbo++;
393 }
394 // Colorize Default Text
395 styler.ColourTo(startLine + offset - 1 - (wbl - wbo), SCE_TCMD_DEFAULT);
396 // Reset Offset to re-process remainder of word
397 offset -= (wbl - wbo);
398 }
399
400 // Skip whitespace - nothing happens if Offset was Reset
401 while ((offset < lengthLine) && (isspacechar(lineBuffer[offset]))) {
402 offset++;
403 }
404 }
405 // Colorize Default Text for remainder of line - currently not lexed
406 styler.ColourTo(endPos, SCE_TCMD_DEFAULT);
407 }
408
409 static void ColouriseTCMDDoc( unsigned int startPos, int length, int /*initStyle*/, WordList *keywordlists[], Accessor &styler )
410 {
411 char lineBuffer[16384];
412
413 styler.StartAt(startPos);
414 styler.StartSegment(startPos);
415 unsigned int linePos = 0;
416 unsigned int startLine = startPos;
417 for (unsigned int i = startPos; i < startPos + length; i++) {
418 lineBuffer[linePos++] = styler[i];
419 if (AtEOL(styler, i) || (linePos >= sizeof(lineBuffer) - 1)) {
420 // End of line (or of line buffer) met, colourise it
421 lineBuffer[linePos] = '\0';
422 ColouriseTCMDLine(lineBuffer, linePos, startLine, i, keywordlists, styler);
423 linePos = 0;
424 startLine = i + 1;
425 }
426 }
427 if (linePos > 0) { // Last line does not have ending characters
428 lineBuffer[linePos] = '\0';
429 ColouriseTCMDLine(lineBuffer, linePos, startLine, startPos + length - 1, keywordlists, styler);
430 }
431 }
432
433 // Convert string to upper case
434 static void StrUpr(char *s) {
435 while (*s) {
436 *s = MakeUpperCase(*s);
437 s++;
438 }
439 }
440
441 // Folding support (for DO, IFF, SWITCH, TEXT, and command groups)
442 static void FoldTCMDDoc(unsigned int startPos, int length, int, WordList *[], Accessor &styler)
443 {
444 int line = styler.GetLine(startPos);
445 int level = styler.LevelAt(line);
446 int levelIndent = 0;
447 unsigned int endPos = startPos + length;
448 char s[16];
449
450 char chPrev = styler.SafeGetCharAt(startPos - 1);
451
452 // Scan for ( and )
453 for (unsigned int i = startPos; i < endPos; i++) {
454
455 int c = styler.SafeGetCharAt(i, '\n');
456 int style = styler.StyleAt(i);
457 bool bLineStart = ((chPrev == '\r') || (chPrev == '\n')) || i == 0;
458
459 if (style == SCE_TCMD_OPERATOR) {
460 // CheckFoldPoint
461 if (c == '(') {
462 levelIndent += 1;
463 } else if (c == ')') {
464 levelIndent -= 1;
465 }
466 }
467
468 if (( bLineStart ) && ( style == SCE_TCMD_WORD )) {
469 for (unsigned int j = 0; j < 10; j++) {
470 if (!iswordchar(styler[i + j])) {
471 break;
472 }
473 s[j] = styler[i + j];
474 s[j + 1] = '\0';
475 }
476
477 StrUpr( s );
478 if ((strcmp(s, "DO") == 0) || (strcmp(s, "IFF") == 0) || (strcmp(s, "SWITCH") == 0) || (strcmp(s, "TEXT") == 0)) {
479 levelIndent++;
480 } else if ((strcmp(s, "ENDDO") == 0) || (strcmp(s, "ENDIFF") == 0) || (strcmp(s, "ENDSWITCH") == 0) || (strcmp(s, "ENDTEXT") == 0)) {
481 levelIndent--;
482 }
483 }
484
485 if (c == '\n') { // line end
486 if (levelIndent > 0) {
487 level |= SC_FOLDLEVELHEADERFLAG;
488 }
489 if (level != styler.LevelAt(line))
490 styler.SetLevel(line, level);
491 level += levelIndent;
492 if ((level & SC_FOLDLEVELNUMBERMASK) < SC_FOLDLEVELBASE)
493 level = SC_FOLDLEVELBASE;
494 line++;
495 // reset state
496 levelIndent = 0;
497 level &= ~SC_FOLDLEVELHEADERFLAG;
498 level &= ~SC_FOLDLEVELWHITEFLAG;
499 }
500
501 chPrev = c;
502 }
503 }
504
505 static const char *const tcmdWordListDesc[] = {
506 "Internal Commands",
507 "Aliases",
508 0
509 };
510
511 LexerModule lmTCMD(SCLEX_TCMD, ColouriseTCMDDoc, "tcmd", FoldTCMDDoc, tcmdWordListDesc);