1 // Scintilla source code edit control
3 ** Lexer for batch files.
5 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
16 #include "Scintilla.h"
20 #include "LexAccessor.h"
22 #include "StyleContext.h"
23 #include "CharacterSet.h"
24 #include "LexerModule.h"
27 using namespace Scintilla
;
30 static bool Is0To9(char ch
) {
31 return (ch
>= '0') && (ch
<= '9');
34 static bool IsAlphabetic(int ch
) {
35 return IsASCII(ch
) && isalpha(ch
);
38 static inline bool AtEOL(Accessor
&styler
, Sci_PositionU i
) {
39 return (styler
[i
] == '\n') ||
40 ((styler
[i
] == '\r') && (styler
.SafeGetCharAt(i
+ 1) != '\n'));
43 // Tests for BATCH Operators
44 static bool IsBOperator(char ch
) {
45 return (ch
== '=') || (ch
== '+') || (ch
== '>') || (ch
== '<') ||
46 (ch
== '|') || (ch
== '?') || (ch
== '*');
49 // Tests for BATCH Separators
50 static bool IsBSeparator(char ch
) {
51 return (ch
== '\\') || (ch
== '.') || (ch
== ';') ||
52 (ch
== '\"') || (ch
== '\'') || (ch
== '/');
55 static void ColouriseBatchLine(
57 Sci_PositionU lengthLine
,
58 Sci_PositionU startLine
,
60 WordList
*keywordlists
[],
63 Sci_PositionU offset
= 0; // Line Buffer Offset
64 Sci_PositionU cmdLoc
; // External Command / Program Location
65 char wordBuffer
[81]; // Word Buffer - large to catch long paths
66 Sci_PositionU wbl
; // Word Buffer Length
67 Sci_PositionU wbo
; // Word Buffer Offset - also Special Keyword Buffer Length
68 WordList
&keywords
= *keywordlists
[0]; // Internal Commands
69 WordList
&keywords2
= *keywordlists
[1]; // External Commands (optional)
71 // CHOICE, ECHO, GOTO, PROMPT and SET have Default Text that may contain Regular Keywords
72 // Toggling Regular Keyword Checking off improves readability
73 // Other Regular Keywords and External Commands / Programs might also benefit from toggling
74 // Need a more robust algorithm to properly toggle Regular Keyword Checking
75 bool continueProcessing
= true; // Used to toggle Regular Keyword Checking
76 // Special Keywords are those that allow certain characters without whitespace after the command
77 // Examples are: cd. cd\ md. rd. dir| dir> echo: echo. path=
78 // Special Keyword Buffer used to determine if the first n characters is a Keyword
79 char sKeywordBuffer
[10]; // Special Keyword Buffer
80 bool sKeywordFound
; // Exit Special Keyword for-loop if found
82 // Skip initial spaces
83 while ((offset
< lengthLine
) && (isspacechar(lineBuffer
[offset
]))) {
86 // Colorize Default Text
87 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_DEFAULT
);
88 // Set External Command / Program Location
91 // Check for Fake Label (Comment) or Real Label - return if found
92 if (lineBuffer
[offset
] == ':') {
93 if (lineBuffer
[offset
+ 1] == ':') {
94 // Colorize Fake Label (Comment) - :: is similar to REM, see http://content.techweb.com/winmag/columns/explorer/2000/21.htm
95 styler
.ColourTo(endPos
, SCE_BAT_COMMENT
);
97 // Colorize Real Label
98 styler
.ColourTo(endPos
, SCE_BAT_LABEL
);
101 // Check for Drive Change (Drive Change is internal command) - return if found
102 } else if ((IsAlphabetic(lineBuffer
[offset
])) &&
103 (lineBuffer
[offset
+ 1] == ':') &&
104 ((isspacechar(lineBuffer
[offset
+ 2])) ||
105 (((lineBuffer
[offset
+ 2] == '\\')) &&
106 (isspacechar(lineBuffer
[offset
+ 3]))))) {
107 // Colorize Regular Keyword
108 styler
.ColourTo(endPos
, SCE_BAT_WORD
);
112 // Check for Hide Command (@ECHO OFF/ON)
113 if (lineBuffer
[offset
] == '@') {
114 styler
.ColourTo(startLine
+ offset
, SCE_BAT_HIDE
);
118 while ((offset
< lengthLine
) && (isspacechar(lineBuffer
[offset
]))) {
122 // Read remainder of line word-at-a-time or remainder-of-word-at-a-time
123 while (offset
< lengthLine
) {
124 if (offset
> startLine
) {
125 // Colorize Default Text
126 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_DEFAULT
);
128 // Copy word from Line Buffer into Word Buffer
130 for (; offset
< lengthLine
&& wbl
< 80 &&
131 !isspacechar(lineBuffer
[offset
]); wbl
++, offset
++) {
132 wordBuffer
[wbl
] = static_cast<char>(tolower(lineBuffer
[offset
]));
134 wordBuffer
[wbl
] = '\0';
137 // Check for Comment - return if found
138 if (CompareCaseInsensitive(wordBuffer
, "rem") == 0) {
139 styler
.ColourTo(endPos
, SCE_BAT_COMMENT
);
142 // Check for Separator
143 if (IsBSeparator(wordBuffer
[0])) {
144 // Check for External Command / Program
145 if ((cmdLoc
== offset
- wbl
) &&
146 ((wordBuffer
[0] == ':') ||
147 (wordBuffer
[0] == '\\') ||
148 (wordBuffer
[0] == '.'))) {
149 // Reset Offset to re-process remainder of word
151 // Colorize External Command / Program
153 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_COMMAND
);
154 } else if (keywords2
.InList(wordBuffer
)) {
155 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_COMMAND
);
157 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_DEFAULT
);
159 // Reset External Command / Program Location
162 // Reset Offset to re-process remainder of word
164 // Colorize Default Text
165 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_DEFAULT
);
167 // Check for Regular Keyword in list
168 } else if ((keywords
.InList(wordBuffer
)) &&
169 (continueProcessing
)) {
170 // ECHO, GOTO, PROMPT and SET require no further Regular Keyword Checking
171 if ((CompareCaseInsensitive(wordBuffer
, "echo") == 0) ||
172 (CompareCaseInsensitive(wordBuffer
, "goto") == 0) ||
173 (CompareCaseInsensitive(wordBuffer
, "prompt") == 0) ||
174 (CompareCaseInsensitive(wordBuffer
, "set") == 0)) {
175 continueProcessing
= false;
177 // Identify External Command / Program Location for ERRORLEVEL, and EXIST
178 if ((CompareCaseInsensitive(wordBuffer
, "errorlevel") == 0) ||
179 (CompareCaseInsensitive(wordBuffer
, "exist") == 0)) {
180 // Reset External Command / Program Location
183 while ((cmdLoc
< lengthLine
) &&
184 (isspacechar(lineBuffer
[cmdLoc
]))) {
188 while ((cmdLoc
< lengthLine
) &&
189 (!isspacechar(lineBuffer
[cmdLoc
]))) {
193 while ((cmdLoc
< lengthLine
) &&
194 (isspacechar(lineBuffer
[cmdLoc
]))) {
197 // Identify External Command / Program Location for CALL, DO, LOADHIGH and LH
198 } else if ((CompareCaseInsensitive(wordBuffer
, "call") == 0) ||
199 (CompareCaseInsensitive(wordBuffer
, "do") == 0) ||
200 (CompareCaseInsensitive(wordBuffer
, "loadhigh") == 0) ||
201 (CompareCaseInsensitive(wordBuffer
, "lh") == 0)) {
202 // Reset External Command / Program Location
205 while ((cmdLoc
< lengthLine
) &&
206 (isspacechar(lineBuffer
[cmdLoc
]))) {
210 // Colorize Regular keyword
211 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_WORD
);
212 // No need to Reset Offset
213 // Check for Special Keyword in list, External Command / Program, or Default Text
214 } else if ((wordBuffer
[0] != '%') &&
215 (wordBuffer
[0] != '!') &&
216 (!IsBOperator(wordBuffer
[0])) &&
217 (continueProcessing
)) {
218 // Check for Special Keyword
219 // Affected Commands are in Length range 2-6
220 // Good that ERRORLEVEL, EXIST, CALL, DO, LOADHIGH, and LH are unaffected
221 sKeywordFound
= false;
222 for (Sci_PositionU keywordLength
= 2; keywordLength
< wbl
&& keywordLength
< 7 && !sKeywordFound
; keywordLength
++) {
224 // Copy Keyword Length from Word Buffer into Special Keyword Buffer
225 for (; wbo
< keywordLength
; wbo
++) {
226 sKeywordBuffer
[wbo
] = static_cast<char>(wordBuffer
[wbo
]);
228 sKeywordBuffer
[wbo
] = '\0';
229 // Check for Special Keyword in list
230 if ((keywords
.InList(sKeywordBuffer
)) &&
231 ((IsBOperator(wordBuffer
[wbo
])) ||
232 (IsBSeparator(wordBuffer
[wbo
])))) {
233 sKeywordFound
= true;
234 // ECHO requires no further Regular Keyword Checking
235 if (CompareCaseInsensitive(sKeywordBuffer
, "echo") == 0) {
236 continueProcessing
= false;
238 // Colorize Special Keyword as Regular Keyword
239 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- wbo
), SCE_BAT_WORD
);
240 // Reset Offset to re-process remainder of word
241 offset
-= (wbl
- wbo
);
244 // Check for External Command / Program or Default Text
245 if (!sKeywordFound
) {
247 // Check for External Command / Program
248 if (cmdLoc
== offset
- wbl
) {
249 // Read up to %, Operator or Separator
250 while ((wbo
< wbl
) &&
251 (wordBuffer
[wbo
] != '%') &&
252 (wordBuffer
[wbo
] != '!') &&
253 (!IsBOperator(wordBuffer
[wbo
])) &&
254 (!IsBSeparator(wordBuffer
[wbo
]))) {
257 // Reset External Command / Program Location
258 cmdLoc
= offset
- (wbl
- wbo
);
259 // Reset Offset to re-process remainder of word
260 offset
-= (wbl
- wbo
);
261 // CHOICE requires no further Regular Keyword Checking
262 if (CompareCaseInsensitive(wordBuffer
, "choice") == 0) {
263 continueProcessing
= false;
265 // Check for START (and its switches) - What follows is External Command \ Program
266 if (CompareCaseInsensitive(wordBuffer
, "start") == 0) {
267 // Reset External Command / Program Location
270 while ((cmdLoc
< lengthLine
) &&
271 (isspacechar(lineBuffer
[cmdLoc
]))) {
274 // Reset External Command / Program Location if command switch detected
275 if (lineBuffer
[cmdLoc
] == '/') {
276 // Skip command switch
277 while ((cmdLoc
< lengthLine
) &&
278 (!isspacechar(lineBuffer
[cmdLoc
]))) {
282 while ((cmdLoc
< lengthLine
) &&
283 (isspacechar(lineBuffer
[cmdLoc
]))) {
288 // Colorize External Command / Program
290 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_COMMAND
);
291 } else if (keywords2
.InList(wordBuffer
)) {
292 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_COMMAND
);
294 styler
.ColourTo(startLine
+ offset
- 1, SCE_BAT_DEFAULT
);
296 // No need to Reset Offset
297 // Check for Default Text
299 // Read up to %, Operator or Separator
300 while ((wbo
< wbl
) &&
301 (wordBuffer
[wbo
] != '%') &&
302 (wordBuffer
[wbo
] != '!') &&
303 (!IsBOperator(wordBuffer
[wbo
])) &&
304 (!IsBSeparator(wordBuffer
[wbo
]))) {
307 // Colorize Default Text
308 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- wbo
), SCE_BAT_DEFAULT
);
309 // Reset Offset to re-process remainder of word
310 offset
-= (wbl
- wbo
);
313 // Check for Argument (%n), Environment Variable (%x...%) or Local Variable (%%a)
314 } else if (wordBuffer
[0] == '%') {
315 // Colorize Default Text
316 styler
.ColourTo(startLine
+ offset
- 1 - wbl
, SCE_BAT_DEFAULT
);
318 // Search to end of word for second % (can be a long path)
319 while ((wbo
< wbl
) &&
320 (wordBuffer
[wbo
] != '%') &&
321 (!IsBOperator(wordBuffer
[wbo
])) &&
322 (!IsBSeparator(wordBuffer
[wbo
]))) {
325 // Check for Argument (%n) or (%*)
326 if (((Is0To9(wordBuffer
[1])) || (wordBuffer
[1] == '*')) &&
327 (wordBuffer
[wbo
] != '%')) {
328 // Check for External Command / Program
329 if (cmdLoc
== offset
- wbl
) {
330 cmdLoc
= offset
- (wbl
- 2);
333 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- 2), SCE_BAT_IDENTIFIER
);
334 // Reset Offset to re-process remainder of word
336 // Check for Expanded Argument (%~...) / Variable (%%~...)
337 } else if (((wbl
> 1) && (wordBuffer
[1] == '~')) ||
338 ((wbl
> 2) && (wordBuffer
[1] == '%') && (wordBuffer
[2] == '~'))) {
339 // Check for External Command / Program
340 if (cmdLoc
== offset
- wbl
) {
341 cmdLoc
= offset
- (wbl
- wbo
);
343 // Colorize Expanded Argument / Variable
344 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- wbo
), SCE_BAT_IDENTIFIER
);
345 // Reset Offset to re-process remainder of word
346 offset
-= (wbl
- wbo
);
347 // Check for Environment Variable (%x...%)
348 } else if ((wordBuffer
[1] != '%') &&
349 (wordBuffer
[wbo
] == '%')) {
351 // Check for External Command / Program
352 if (cmdLoc
== offset
- wbl
) {
353 cmdLoc
= offset
- (wbl
- wbo
);
355 // Colorize Environment Variable
356 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- wbo
), SCE_BAT_IDENTIFIER
);
357 // Reset Offset to re-process remainder of word
358 offset
-= (wbl
- wbo
);
359 // Check for Local Variable (%%a)
362 (wordBuffer
[1] == '%') &&
363 (wordBuffer
[2] != '%') &&
364 (!IsBOperator(wordBuffer
[2])) &&
365 (!IsBSeparator(wordBuffer
[2]))) {
366 // Check for External Command / Program
367 if (cmdLoc
== offset
- wbl
) {
368 cmdLoc
= offset
- (wbl
- 3);
370 // Colorize Local Variable
371 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- 3), SCE_BAT_IDENTIFIER
);
372 // Reset Offset to re-process remainder of word
375 // Check for Environment Variable (!x...!)
376 } else if (wordBuffer
[0] == '!') {
377 // Colorize Default Text
378 styler
.ColourTo(startLine
+ offset
- 1 - wbl
, SCE_BAT_DEFAULT
);
380 // Search to end of word for second ! (can be a long path)
381 while ((wbo
< wbl
) &&
382 (wordBuffer
[wbo
] != '!') &&
383 (!IsBOperator(wordBuffer
[wbo
])) &&
384 (!IsBSeparator(wordBuffer
[wbo
]))) {
387 if (wordBuffer
[wbo
] == '!') {
389 // Check for External Command / Program
390 if (cmdLoc
== offset
- wbl
) {
391 cmdLoc
= offset
- (wbl
- wbo
);
393 // Colorize Environment Variable
394 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- wbo
), SCE_BAT_IDENTIFIER
);
395 // Reset Offset to re-process remainder of word
396 offset
-= (wbl
- wbo
);
398 // Check for Operator
399 } else if (IsBOperator(wordBuffer
[0])) {
400 // Colorize Default Text
401 styler
.ColourTo(startLine
+ offset
- 1 - wbl
, SCE_BAT_DEFAULT
);
402 // Check for Comparison Operator
403 if ((wordBuffer
[0] == '=') && (wordBuffer
[1] == '=')) {
404 // Identify External Command / Program Location for IF
407 while ((cmdLoc
< lengthLine
) &&
408 (isspacechar(lineBuffer
[cmdLoc
]))) {
411 // Colorize Comparison Operator
412 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- 2), SCE_BAT_OPERATOR
);
413 // Reset Offset to re-process remainder of word
415 // Check for Pipe Operator
416 } else if (wordBuffer
[0] == '|') {
417 // Reset External Command / Program Location
418 cmdLoc
= offset
- wbl
+ 1;
420 while ((cmdLoc
< lengthLine
) &&
421 (isspacechar(lineBuffer
[cmdLoc
]))) {
424 // Colorize Pipe Operator
425 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- 1), SCE_BAT_OPERATOR
);
426 // Reset Offset to re-process remainder of word
428 // Check for Other Operator
430 // Check for > Operator
431 if (wordBuffer
[0] == '>') {
432 // Turn Keyword and External Command / Program checking back on
433 continueProcessing
= true;
435 // Colorize Other Operator
436 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- 1), SCE_BAT_OPERATOR
);
437 // Reset Offset to re-process remainder of word
440 // Check for Default Text
442 // Read up to %, Operator or Separator
443 while ((wbo
< wbl
) &&
444 (wordBuffer
[wbo
] != '%') &&
445 (wordBuffer
[wbo
] != '!') &&
446 (!IsBOperator(wordBuffer
[wbo
])) &&
447 (!IsBSeparator(wordBuffer
[wbo
]))) {
450 // Colorize Default Text
451 styler
.ColourTo(startLine
+ offset
- 1 - (wbl
- wbo
), SCE_BAT_DEFAULT
);
452 // Reset Offset to re-process remainder of word
453 offset
-= (wbl
- wbo
);
455 // Skip next spaces - nothing happens if Offset was Reset
456 while ((offset
< lengthLine
) && (isspacechar(lineBuffer
[offset
]))) {
460 // Colorize Default Text for remainder of line - currently not lexed
461 styler
.ColourTo(endPos
, SCE_BAT_DEFAULT
);
464 static void ColouriseBatchDoc(
465 Sci_PositionU startPos
,
468 WordList
*keywordlists
[],
471 char lineBuffer
[1024];
473 styler
.StartAt(startPos
);
474 styler
.StartSegment(startPos
);
475 Sci_PositionU linePos
= 0;
476 Sci_PositionU startLine
= startPos
;
477 for (Sci_PositionU i
= startPos
; i
< startPos
+ length
; i
++) {
478 lineBuffer
[linePos
++] = styler
[i
];
479 if (AtEOL(styler
, i
) || (linePos
>= sizeof(lineBuffer
) - 1)) {
480 // End of line (or of line buffer) met, colourise it
481 lineBuffer
[linePos
] = '\0';
482 ColouriseBatchLine(lineBuffer
, linePos
, startLine
, i
, keywordlists
, styler
);
487 if (linePos
> 0) { // Last line does not have ending characters
488 lineBuffer
[linePos
] = '\0';
489 ColouriseBatchLine(lineBuffer
, linePos
, startLine
, startPos
+ length
- 1,
490 keywordlists
, styler
);
494 static const char *const batchWordListDesc
[] = {
500 LexerModule
lmBatch(SCLEX_BATCH
, ColouriseBatchDoc
, "batch", 0, batchWordListDesc
);