ReactOS 0.4.15-dev-7953-g1f49173
parser.c
Go to the documentation of this file.
1/*
2 * PARSER.C - Command-line Lexical Analyzer/Tokenizer and Parser.
3 */
4
5#include "precomp.h"
6
7/*
8 * Defines for enabling different Windows' CMD compatibility behaviours.
9 */
10
11/* Enable this define for command echoer compatibility */
12#define MSCMD_ECHO_COMMAND_COMPAT
13
14/* Enable this define for parser quirks (see UnParseToken() for more details) */
15#define MSCMD_PARSER_BUGS
16
17/* Enable this define for parenthesized blocks parsing quirks */
18// #define MSCMD_PARENS_PARSE_BUGS
19
20/* Enable this define for redirection parsing quirks */
21#define MSCMD_REDIR_PARSE_BUGS
22
23/* Enable this define for allowing '&' commands with an empty RHS.
24 * The default behaviour is to just return the LHS instead.
25 * See ParseCommandBinaryOp() for details. */
26// #define MSCMD_MULTI_EMPTY_RHS
27
28
29/*
30 * Parser debugging support. These flags are global so that their values can be
31 * modified at runtime from a debugger. They correspond to the public Windows'
32 * cmd!fDumpTokens and cmd!fDumpParse booleans.
33 * (Same names are used for compatibility as they are documented online.)
34 */
37
38#define C_OP_LOWEST C_MULTI
39#define C_OP_HIGHEST C_PIPE
40static const TCHAR OpString[][3] = { _T("&"), _T("||"), _T("&&"), _T("|") };
41
42static const TCHAR RedirString[][3] = { _T("<"), _T(">"), _T(">>") };
43
44static const TCHAR* const IfOperatorString[] =
45{
48 /* Standard */
49 _T("errorlevel"),
50 _T("exist"),
51
52 /* Extended */
53 _T("cmdextversion"),
54 _T("defined"),
55#define IF_MAX_UNARY IF_DEFINED
56
59 /* Standard */
60 _T("=="),
61
62 /* Extended */
63 _T("equ"),
64 _T("neq"),
65 _T("lss"),
66 _T("leq"),
67 _T("gtr"),
68 _T("geq"),
69#define IF_MAX_COMPARISON IF_GEQ
70};
71
72static __inline BOOL IsSeparator(TCHAR Char)
73{
74 return _istspace(Char) || (Char && !!_tcschr(STANDARD_SEPS, Char));
75}
76
77typedef enum _TOK_TYPE
78{
86
87/* Scratch buffer for temporary command substitutions / expansions */
89
90/*static*/ BOOL bParseError;
95
98
101#ifndef MSCMD_PARSER_BUGS
102static BOOL bReparseToken = FALSE;
103static PTCHAR LastCurTokPos;
104#endif
105static INT InsideBlock = 0;
106
108{
110 ParsePos = Pos;
112}
113
114/*
115 * This function "refetches" the last parsed token back into the stream
116 * for later reparsing -- since the way of lexing it is context-dependent.
117 * This "feature" is at the root of many obscure CMD parsing quirks,
118 * due to the fact this feature is in opposition with line-continuation.
119 * Indeed, when a stream of characters has a line-continuation, the lexer-
120 * parser will parse the stream up to the end of the line, then will
121 * reset the parser state and position back to the beginning of the line
122 * before accepting the rest of the character stream and continuing
123 * parsing them. This means that all the non-parsed characters before the
124 * line-continuation have been lost. Of course, their parsed form is now
125 * within the current parsed token. However, suppose now we need to
126 * unparse this token for reparsing it a different way later on. If we
127 * somehow pushed the already-parsed current token back into the beginning
128 * of the character stream, besides the complications of moving up the
129 * characters in the stream buffer, we would basically have "new" data
130 * that has been already parsed one way, to be now parsed another way.
131 * If instead we had saved somehow the unparsed form of the token, and
132 * we push back that form into the stream buffer for reparsing, we would
133 * encounter again the line-continuation, that, depending on which
134 * context the token is reparsed, would cause problems:
135 * e.g. in the case of REM command parsing, the parser would stop at the
136 * first line-continuation.
137 *
138 * When MSCMD_PARSER_BUGS is undefined, the UnParseToken() / ParseToken()
139 * cycle keeps the current token in its buffer, but also saves the start
140 * position corresponding to the batch of characters that have been parsed
141 * during the last line-continuation. The next ParseToken() would then
142 * reparse these latest charcters and the result replaces the last part
143 * in the current token.
144 *
145 * For example, a first parsing of
146 * foo^\n
147 * bar^\n
148 * baz
149 * would result in the current token "foobarbaz", where the start position
150 * corresponding to the batch of characters parsed during the last line-continuation
151 * being pointing at "baz". The stream buffer only contains "baz" (and following data).
152 * Then UnParseToken() saves this info so that at the next ParseToken(), the "baz"
153 * part of the stream buffer gets reparsed (possibly differently) and the result
154 * would replace the "baz" part in the current token.
155 *
156 * If MSCMD_PARSER_BUGS is defined however, then the behaviour of the Windows' CMD
157 * parser applies: in the example above, the last ParseToken() call would completely
158 * replace the current token "foobarbaz" with the new result of the parsing of "baz".
159 */
161{
163
164 /* Debugging support */
165 if (fDumpTokens)
166 ConOutPrintf(_T("Ungetting: '%s'\n"), ParsePos);
167
168#ifndef MSCMD_PARSER_BUGS
169 bReparseToken = TRUE;
170#endif
171}
172
174{
175 *CurrentToken = 0;
177 InsideBlock = 0;
178
179#ifndef MSCMD_PARSER_BUGS
180 bReparseToken = FALSE;
181 LastCurTokPos = NULL;
182#endif
183
185}
186
188{
189 TCHAR Char;
190
191 if (bParseError)
192 return 0;
193
194restart:
195 /*
196 * Although CRs can be injected into a line via an environment
197 * variable substitution, the parser ignores them - they won't
198 * even separate tokens.
199 */
200 do
201 {
202 Char = *ParsePos++;
203 }
204 while (Char == _T('\r'));
205
206 if (!Char) --ParsePos;
207 if (!Char && bLineContinuations)
208 {
209 if (!ReadLine(ParseLine, TRUE))
210 {
211 /* ^C pressed, or line was too long */
212 //
213 // FIXME: Distinguish with respect to BATCH end of file !!
214 //
216 }
217 else
218 {
220 if (*ParsePos)
221 goto restart;
222 }
223 }
224 return Char;
225}
226
228{
229 /* Only display the first error we encounter */
230 if (!bParseError)
233}
234
235static __inline VOID ParseError(VOID)
236{
238}
239
240static TOK_TYPE
242 IN TCHAR PrefixOperator OPTIONAL,
243 IN TCHAR ExtraEnd OPTIONAL,
244 IN PCTSTR Separators OPTIONAL,
246{
248 PTCHAR CurrentTokStart = CurrentToken;
249 PTCHAR Out = CurrentTokStart;
250 TCHAR Char;
251 BOOL bInQuote = FALSE;
252
253#ifndef MSCMD_PARSER_BUGS
254 if (bReparseToken)
255 {
256 bReparseToken = FALSE;
257
258 /*
259 * We will append the part to be reparsed to the old one
260 * (still present in CurrentToken).
261 */
262 CurrentTokStart = LastCurTokPos;
263 Out = CurrentTokStart;
264 }
265 else
266 {
267 LastCurTokPos = CurrentToken;
268 }
269#endif
270
271 /* Start with what we have at current ParsePos */
273
274 for (Char = ParseChar(); Char && Char != _T('\n'); Char = ParseChar())
275 {
276 bInQuote ^= (Char == _T('"'));
277 if (!bInQuote)
278 {
279 if (Separators != NULL)
280 {
281 if (_istspace(Char) || !!_tcschr(Separators, Char))
282 {
283 /* Skip leading separators */
284 if (Out == CurrentTokStart)
285 continue;
286 break;
287 }
288 }
289
290 /* Check for prefix operator */
291 if ((Out == CurrentTokStart) && (Char == PrefixOperator))
292 break;
293
294 /*
295 * Check for numbered redirection.
296 *
297 * For this purpose, we check whether this is a number, that is
298 * in first position in the current parsing buffer (remember that
299 * ParsePos points to the next character) or is preceded by a
300 * whitespace-like separator, including standard command operators
301 * (excepting '@' !) and double-quotes.
302 */
303 if ( _istdigit(Char) &&
304 (ParsePos == &OldParsePos[1] ||
305 IsSeparator(ParsePos[-2]) ||
306 !!_tcschr(_T("()&|\""), ParsePos[-2])) &&
307 (*ParsePos == _T('<') || *ParsePos == _T('>')) )
308 {
309 break;
310 }
311
312 /* Check for other delimiters / operators */
313 if (Char == ExtraEnd)
314 break;
315 if (InsideBlock && Char == _T(')'))
316 break;
317 if (_tcschr(_T("&|<>"), Char))
318 break;
319
320 if (bHandleContinuations && (Char == _T('^')))
321 {
322 Char = ParseChar();
323 /* Eat up a \n, allowing line continuation */
324 if (Char == _T('\n'))
325 {
326#ifndef MSCMD_PARSER_BUGS
327 LastCurTokPos = Out;
328#endif
329 Char = ParseChar();
330 }
331 /* Next character is a forced literal */
332
333 if (Out == CurrentTokStart)
334 {
335 /* Ignore any prefix operator if we don't start a new command block */
337 PrefixOperator = 0;
338 }
339 }
340 }
341 if (Out == &CurrentToken[CMDLINE_LENGTH - 1])
342 break;
343 *Out++ = Char;
344
345 // PrefixOperator = 0;
346 }
347
348 /*
349 * We exited the parsing loop. If the current character is the first one
350 * (Out == CurrentTokStart), interpret it as an operator. Otherwise,
351 * terminate the current token (type TOK_NORMAL) and keep the current
352 * character so that it can be refetched as an operator at the next call.
353 */
354
355 if (Out != CurrentTokStart)
356 {
358 }
359 /*
360 * Else we have an operator.
361 */
362 else if (Char == _T('@'))
363 {
364 Type = TOK_OPERATOR; // TOK_QUIET / TOK_PREFIX_OPERATOR
365 *Out++ = Char;
366 Char = ParseChar();
367 }
368 else if (Char == _T('('))
369 {
371 *Out++ = Char;
372 Char = ParseChar();
373 }
374 else if (Char == _T(')'))
375 {
377 *Out++ = Char;
378 Char = ParseChar();
379 }
380 else if (Char == _T('&') || Char == _T('|'))
381 {
383 *Out++ = Char;
384 Char = ParseChar();
385 /* Check for '&&' or '||' */
386 if (Char == Out[-1])
387 {
388 *Out++ = Char;
389 Char = ParseChar();
390 }
391 }
392 else if ( _istdigit(Char) ||
393 (Char == _T('<') || Char == _T('>')) )
394 {
396 if (_istdigit(Char))
397 {
398 *Out++ = Char;
399 Char = ParseChar();
400 }
401 /* By construction (see the while-loop above),
402 * the next character must be a redirection. */
403 ASSERT(Char == _T('<') || Char == _T('>'));
404 *Out++ = Char;
405 Char = ParseChar();
406 if (Char == Out[-1])
407 {
408 /* Strangely, the tokenizer allows << as well as >>... (it
409 * will cause an error when trying to parse it though) */
410 *Out++ = Char;
411 Char = ParseChar();
412 }
413 if (Char == _T('&'))
414 {
415 *Out++ = Char;
416 while (IsSeparator(Char = ParseChar()))
417 ;
418 if (_istdigit(Char))
419 {
420 *Out++ = Char;
421 Char = ParseChar();
422 }
423 }
424 }
425 else
426 {
427 Type = TOK_END;
428 *Out++ = Char;
429 }
430 *Out = _T('\0');
431
432 /*
433 * Rewind the parsing position, so that the current character can be
434 * refetched later on. However do this only if it is not NULL and if
435 * this is not TOK_END, since we do not want to reparse later the line
436 * termination (we could enter into infinite loops, or, in case of line
437 * continuation, get unwanted "More?" prompts).
438 */
439 if (Char != 0 && Type != TOK_END)
440 --ParsePos;
441
442 /* Debugging support */
443 if (fDumpTokens)
444 ConOutPrintf(_T("ParseToken: (%d) '%s'\n"), Type, CurrentToken);
445
446 return (CurrentTokenType = Type);
447}
448
449static __inline INT
451 IN TCHAR ExtraEnd OPTIONAL,
452 IN PCTSTR Separators OPTIONAL)
453{
454 return ParseTokenEx(0, ExtraEnd, Separators, bHandleContinuations);
455}
456
457
458static PARSED_COMMAND*
461 IN PCTSTR CmdHead OPTIONAL,
462 IN PCTSTR CmdTail OPTIONAL)
463{
465
466 switch (Type)
467 {
468 case C_COMMAND:
469 case C_REM:
470 {
471 SIZE_T CmdHeadLen = _tcslen(CmdHead) + 1;
472 SIZE_T CmdTailLen = _tcslen(CmdTail) + 1;
473
475 Command.First[CmdHeadLen + CmdTailLen]));
476 if (!Cmd)
477 return NULL;
478
479 Cmd->Type = Type;
480 Cmd->Next = NULL;
481 Cmd->Subcommands = NULL;
482 Cmd->Redirections = NULL; /* Is assigned by the calling function */
483 memcpy(Cmd->Command.First, CmdHead, CmdHeadLen * sizeof(TCHAR));
484 Cmd->Command.Rest = Cmd->Command.First + CmdHeadLen;
485 memcpy(Cmd->Command.Rest, CmdTail, CmdTailLen * sizeof(TCHAR));
486 return Cmd;
487 }
488
489 case C_QUIET:
490 case C_BLOCK:
491 case C_MULTI:
492 case C_OR:
493 case C_AND:
494 case C_PIPE:
495 {
496 Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
497 if (!Cmd)
498 return NULL;
499
500 Cmd->Type = Type;
501 Cmd->Next = NULL;
502 Cmd->Subcommands = NULL;
503 Cmd->Redirections = NULL; /* For C_BLOCK only: is assigned by the calling function */
504 return Cmd;
505 }
506
507 case C_FOR:
508 case C_IF:
509 {
510 Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
511 if (!Cmd)
512 return NULL;
513
514 memset(Cmd, 0, sizeof(PARSED_COMMAND));
515 Cmd->Type = Type;
516 return Cmd;
517 }
518
519 default:
520 ERR("Unknown command type 0x%x\n", Type);
521 ASSERT(FALSE);
522 return NULL;
523 }
524}
525
526VOID
529{
530 if (Cmd->Subcommands)
531 FreeCommand(Cmd->Subcommands);
532 if (Cmd->Next)
533 FreeCommand(Cmd->Next);
534 FreeRedirection(Cmd->Redirections);
535 if (Cmd->Type == C_FOR)
536 {
537 cmd_free(Cmd->For.Params);
538 cmd_free(Cmd->For.List);
539 }
540 else if (Cmd->Type == C_IF)
541 {
542 cmd_free(Cmd->If.LeftArg);
543 cmd_free(Cmd->If.RightArg);
544 }
545 cmd_free(Cmd);
546}
547
548
549/* Parse redirections and append them to the list */
550static BOOL
553{
554 PTSTR Tok = CurrentToken;
555 REDIRECTION* Redir;
556 REDIR_MODE RedirMode;
557 BYTE Number;
558
559 if ( !(*Tok == _T('<') || *Tok == _T('>')) &&
560 !(_istdigit(*Tok) &&
561 (Tok[1] == _T('<') || Tok[1] == _T('>')) ) )
562 {
564 return FALSE;
565 }
568
569 if (_istdigit(*Tok))
570 Number = *Tok++ - _T('0');
571 else
572 Number = *Tok == _T('<') ? 0 : 1;
573
574 if (*Tok++ == _T('<'))
575 {
576 RedirMode = REDIR_READ;
577 /* Forbid '<<' */
578 if (*Tok == _T('<'))
579 goto fail;
580 }
581 else
582 {
583 RedirMode = REDIR_WRITE;
584 if (*Tok == _T('>'))
585 {
586 RedirMode = REDIR_APPEND;
587 Tok++;
588 }
589 }
590
591 if (*Tok == _T('&'))
592 {
593 /* This is a handle redirection: the next character must be one single digit */
594 if (!(_istdigit(Tok[1]) && !Tok[2]))
595 goto fail;
596 }
597 else
598#ifndef MSCMD_REDIR_PARSE_BUGS
599 if (!*Tok)
600 /* The file name was not part of this token, so it will be the next one */
601#else
602 /* Get rid of what possibly remains in the token, and retrieve the next one */
603#endif
604 {
606 goto fail;
607 Tok = CurrentToken;
608 }
609
610 /* If a redirection for this handle number already exists, delete it */
611 while ((Redir = *List))
612 {
613 if (Redir->Number == Number)
614 {
615 *List = Redir->Next;
616 cmd_free(Redir);
617 continue;
618 }
619 List = &Redir->Next;
620 }
621
623 if (!Redir)
624 {
625 WARN("Cannot allocate memory for Redir!\n");
626 goto fail;
627 }
628 Redir->Next = NULL;
630 Redir->Number = Number;
631 Redir->Mode = RedirMode;
632 _tcscpy(Redir->Filename, Tok);
633 *List = Redir;
634 return TRUE;
635
636fail:
637 ParseError();
639 *List = NULL;
640 return FALSE;
641}
642
643static __inline PARSED_COMMAND*
646
647/* Parse a parenthesized block */
648static PARSED_COMMAND*
650 IN OUT REDIRECTION** RedirList)
651{
652 PARSED_COMMAND *Cmd, *Sub, **NextPtr;
653
655 if (!Cmd)
656 {
657 WARN("Cannot allocate memory for Cmd!\n");
658 ParseError();
659 return NULL;
660 }
661
662 /* Read the block contents */
663 NextPtr = &Cmd->Subcommands;
664 ++InsideBlock;
665 while (TRUE)
666 {
667 /*
668 * Windows' CMD compatibility: Strip leading newlines in the block.
669 *
670 * Note that this behaviour is buggy, especially when MSCMD_PARSER_BUGS is defined!
671 * For example:
672 * (foo^\n
673 * bar)
674 * would be parsed ultimately as: '(', 'bar', ')' because the "foo^"
675 * part would be discarded due to the UnParseToken() call, since this
676 * function doesn't work across line continuations.
677 */
678 while (ParseToken(0, STANDARD_SEPS) == TOK_END && *CurrentToken == _T('\n'))
679 ;
680 if (*CurrentToken && *CurrentToken != _T('\n'))
681 UnParseToken();
682
683 /* Break early if we have nothing else to read. We will also fail
684 * due to the fact we haven't encountered any closing parenthesis. */
685 if (!*CurrentToken /* || *CurrentToken == _T('\n') */)
686 {
688 break;
689 }
690
691 /*
692 * NOTE: Windows' CMD uses a "CRLF" operator when dealing with
693 * newlines in parenthesized blocks, as an alternative to the
694 * '&' command-separation operator.
695 */
696
698 if (Sub)
699 {
700 *NextPtr = Sub;
701 NextPtr = &Sub->Next;
702 }
703 else if (bParseError)
704 {
705 --InsideBlock;
707 return NULL;
708 }
709
711 break;
712
713 /* Skip past the \n */
714 }
715 --InsideBlock;
716
717 /* Fail if the block was not terminated, or if we have
718 * an empty block, i.e. "( )", considered invalid. */
719 if ((CurrentTokenType != TOK_END_BLOCK) || (Cmd->Subcommands == NULL))
720 {
721 ParseError();
723 return NULL;
724 }
725
726 /* Process any trailing redirections and append them to the list */
727#ifndef MSCMD_REDIR_PARSE_BUGS
729 {
730 if (!ParseRedirection(RedirList))
731 {
733 return NULL;
734 }
735 }
736#else
737 while (ParseToken(0, STANDARD_SEPS) != TOK_END)
738 {
739 if (!ParseRedirection(RedirList))
740 {
741 /* If an actual error happened in ParseRedirection(), bail out */
742 if (bParseError)
743 {
745 return NULL;
746 }
747 /* Otherwise it just returned FALSE because the current token
748 * is not a redirection. Unparse the token and refetch it. */
749 break;
750 }
751 }
752#endif
754 {
755 /*
756 * Windows' CMD compatibility: Unparse the current token.
757 *
758 * Note that this behaviour is buggy, especially when MSCMD_PARSER_BUGS is defined!
759 * For example:
760 * (foo^\n
761 * bar)
762 * would be parsed ultimately as: '(', 'bar', ')' because the "foo^"
763 * part would be discarded due to the UnParseToken() call, since this
764 * function doesn't work across line continuations.
765 */
766 UnParseToken();
767
768 /*
769 * Since it is expected that when ParseBlock() returns, the next
770 * token is already fetched, call ParseToken() again to compensate.
771 */
773 }
774
775 return Cmd;
776}
777
778/* Parse an IF statement */
779static PARSED_COMMAND*
781{
783
785 if (!Cmd)
786 {
787 WARN("Cannot allocate memory for Cmd!\n");
788 ParseError();
789 return NULL;
790 }
791
792 if (bEnableExtensions && (_tcsicmp(CurrentToken, _T("/I")) == 0))
793 {
794 Cmd->If.Flags |= IFFLAG_IGNORECASE;
796 }
797 if (_tcsicmp(CurrentToken, _T("not")) == 0)
798 {
799 Cmd->If.Flags |= IFFLAG_NEGATE;
801 }
802
804 goto error;
805
806 /* Check for unary operators */
807 for (; Cmd->If.Operator <= IF_MAX_UNARY; Cmd->If.Operator++)
808 {
809 /* Skip the extended operators if the extensions are disabled */
810 if (!bEnableExtensions && (Cmd->If.Operator >= IF_CMDEXTVERSION))
811 continue;
812
813 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
814 {
816 goto error;
817 Cmd->If.RightArg = cmd_dup(CurrentToken);
818 goto condition_done;
819 }
820 }
821
822 /* It must be a two-argument (comparison) operator. It could be ==, so
823 * the equals sign can't be treated as whitespace here. */
824 Cmd->If.LeftArg = cmd_dup(CurrentToken);
825 ParseToken(0, _T(",;"));
826
827 /* The right argument can come immediately after == */
828 if (_tcsnicmp(CurrentToken, _T("=="), 2) == 0 && CurrentToken[2])
829 {
830 Cmd->If.RightArg = cmd_dup(&CurrentToken[2]);
831 goto condition_done;
832 }
833
834 // Cmd->If.Operator == IF_MAX_UNARY + 1;
835 for (; Cmd->If.Operator <= IF_MAX_COMPARISON; Cmd->If.Operator++)
836 {
837 /* Skip the extended operators if the extensions are disabled */
838 if (!bEnableExtensions && (Cmd->If.Operator >= IF_EQU)) // (Cmd->If.Operator > IF_STRINGEQ)
839 continue;
840
841 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
842 {
844 goto error;
845 Cmd->If.RightArg = cmd_dup(CurrentToken);
846 goto condition_done;
847 }
848 }
849 goto error;
850
851condition_done:
852 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
853 if (Cmd->Subcommands == NULL)
854 goto error;
855 if (_tcsicmp(CurrentToken, _T("else")) == 0)
856 {
857 Cmd->Subcommands->Next = ParseCommandOp(C_OP_LOWEST);
858 if (Cmd->Subcommands->Next == NULL)
859 goto error;
860 }
861
862 return Cmd;
863
864error:
866 ParseError();
867 return NULL;
868}
869
870/*
871 * Parse a FOR command.
872 * Syntax is: FOR [options] %var IN (list) DO command
873 */
874static PARSED_COMMAND*
876{
878
879 /* Use the scratch buffer */
881 PTCHAR Pos = List;
882
884 if (!Cmd)
885 {
886 WARN("Cannot allocate memory for Cmd!\n");
887 ParseError();
888 return NULL;
889 }
890
891 /* Skip the extended FOR syntax if extensions are disabled */
893 goto parseForBody;
894
895 while (TRUE)
896 {
897 if (_tcsicmp(CurrentToken, _T("/D")) == 0)
898 {
899 Cmd->For.Switches |= FOR_DIRS;
900 }
901 else if (_tcsicmp(CurrentToken, _T("/F")) == 0)
902 {
903 Cmd->For.Switches |= FOR_F;
904 if (!Cmd->For.Params)
905 {
907 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
908 break;
909 Cmd->For.Params = cmd_dup(CurrentToken);
910 }
911 }
912 else if (_tcsicmp(CurrentToken, _T("/L")) == 0)
913 {
914 Cmd->For.Switches |= FOR_LOOP;
915 }
916 else if (_tcsicmp(CurrentToken, _T("/R")) == 0)
917 {
918 Cmd->For.Switches |= FOR_RECURSIVE;
919 if (!Cmd->For.Params)
920 {
922 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
923 break;
925 Cmd->For.Params = cmd_dup(CurrentToken);
926 }
927 }
928 else
929 {
930 break;
931 }
932
934 }
935
936 /* Make sure there aren't two different switches specified
937 * at the same time, unless they're /D and /R */
938 if ((Cmd->For.Switches & (Cmd->For.Switches - 1)) != 0
939 && Cmd->For.Switches != (FOR_DIRS | FOR_RECURSIVE))
940 {
941 goto error;
942 }
943
944parseForBody:
945
946 /* Variable name should be % and just one other character */
947 if (CurrentToken[0] != _T('%') || _tcslen(CurrentToken) != 2)
948 goto error;
949 Cmd->For.Variable = CurrentToken[1];
950
952 if (_tcsicmp(CurrentToken, _T("in")) != 0)
953 goto error;
954
956 goto error;
957
958 while (TRUE)
959 {
960 /* Pretend we're inside a block so the tokenizer will stop on ')' */
961 ++InsideBlock;
963 --InsideBlock;
964
966 break;
967
968 /* Skip past the \n */
969 if ((CurrentTokenType == TOK_END) && *CurrentToken == _T('\n'))
970 continue;
971
973 goto error;
974
975 if (Pos != List)
976 *Pos++ = _T(' ');
977
979 goto error;
981 }
982 *Pos = _T('\0');
983 Cmd->For.List = cmd_dup(List);
984
986 if (_tcsicmp(CurrentToken, _T("do")) != 0)
987 goto error;
988
989 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
990 if (Cmd->Subcommands == NULL)
991 goto error;
992
993 return Cmd;
994
995error:
997 ParseError();
998 return NULL;
999}
1000
1001/* Parse a REM command */
1002static PARSED_COMMAND*
1004{
1006
1007 /* The common scratch buffer already contains the name of the command */
1008 PTSTR ParsedLine = TempBuf;
1009
1010 PTCHAR Pos = ParsedLine + _tcslen(ParsedLine) + 1;
1011 SIZE_T TailOffset = Pos - ParsedLine;
1012
1013 /* Build a minimal command for REM, so that it can still get through the batch echo unparsing */
1014
1015 /* Unparse the current token, so as to emulate the REM command parsing
1016 * behaviour of Windows' CMD, that discards everything before the last
1017 * line continuation. */
1018 UnParseToken();
1019
1020 /*
1021 * Ignore the rest of the line, without any line continuation (but eat the caret).
1022 * We cannot simply set bLineContinuations to TRUE or FALSE, because we want (only
1023 * for the REM command), even when bLineContinuations == FALSE, to get the caret,
1024 * otherwise it would be ignored.
1025 */
1026 while (ParseTokenEx(0, 0, NULL, FALSE) != TOK_END)
1027 {
1028 if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
1029 {
1030 ParseError();
1031 return NULL;
1032 }
1034 }
1035 *Pos = _T('\0');
1036
1038 ParsedLine,
1039 ParsedLine + TailOffset);
1040 if (!Cmd)
1041 {
1042 WARN("Cannot allocate memory for Cmd!\n");
1043 ParseError();
1044 return NULL;
1045 }
1046 return Cmd;
1047}
1048
1049/* Parse a command */
1050static PARSED_COMMAND*
1052 IN OUT REDIRECTION** RedirList)
1053{
1055 PARSED_COMMAND* (*Func)(VOID);
1056
1057 /* Use the scratch buffer */
1058 PTSTR ParsedLine = TempBuf;
1059
1060 /* We need to copy the current token because it's going to be changed below by the ParseToken() calls */
1061 PTCHAR Pos = _stpcpy(ParsedLine, CurrentToken) + 1;
1062 SIZE_T TailOffset = Pos - ParsedLine;
1063
1064 /* Check for special forms */
1065 if ((Func = ParseFor, _tcsicmp(ParsedLine, _T("FOR")) == 0) ||
1066 (Func = ParseIf, _tcsicmp(ParsedLine, _T("IF")) == 0) ||
1067 (Func = ParseRem, _tcsicmp(ParsedLine, _T("REM")) == 0))
1068 {
1069 PTCHAR pHelp;
1070
1072
1073 if ((pHelp = _tcsstr(CurrentToken, _T("/?"))) &&
1074 (Func == ParseIf ? (pHelp[2] == _T('/') || pHelp[2] == 0) : TRUE))
1075 {
1076 /* /? was found within the first token */
1078 }
1079 else
1080 {
1081 pHelp = NULL;
1082 }
1083 if (pHelp && (CurrentTokenType == TOK_NORMAL))
1084 {
1085 /* We encountered /? first, but is followed
1086 * by another token: that's an error. */
1087 ParseError();
1088 return NULL;
1089 }
1090
1091 /* Do actual parsing only if no help is present */
1092 if (!pHelp)
1093 {
1094 /* FOR and IF commands cannot have leading redirection, but REM can */
1095 if (*RedirList && ((Func == ParseFor) || (Func == ParseIf)))
1096 {
1097 /* Display the culprit command and fail */
1098 ParseErrorEx(ParsedLine);
1099 return NULL;
1100 }
1101
1102 return Func();
1103 }
1104
1105 /* Otherwise, run FOR,IF,REM as regular commands only for help support */
1106 if (Pos + _tcslen(_T("/?")) >= &ParsedLine[CMDLINE_LENGTH])
1107 {
1108 ParseError();
1109 return NULL;
1110 }
1111 Pos = _stpcpy(Pos, _T("/?"));
1112 }
1113 else
1114 {
1115 ParseToken(0, NULL);
1116 }
1117
1118 /* Now get the tail */
1119 while (CurrentTokenType != TOK_END)
1120 {
1122 {
1123 if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
1124 {
1125 ParseError();
1126 return NULL;
1127 }
1129 }
1130#ifndef MSCMD_REDIR_PARSE_BUGS
1132 {
1133 /* Process any trailing redirections and append them to the list */
1135 {
1136 if (!ParseRedirection(RedirList))
1137 return NULL;
1138
1140 }
1142 break;
1143
1144 /* Unparse the current token, and reparse it below with no separators */
1145 UnParseToken();
1146 }
1147 else
1148 {
1149 /* There is no need to do a UnParseToken() / ParseToken() cycle */
1150 break;
1151 }
1152#else
1153 else
1154 {
1155 /* Process any trailing redirections and append them to the list */
1157
1159
1160 while (CurrentTokenType != TOK_END)
1161 {
1162 if (!ParseRedirection(RedirList))
1163 {
1164 /* If an actual error happened in ParseRedirection(), bail out */
1165 if (bParseError)
1166 return NULL;
1167
1168 /* Otherwise it just returned FALSE because the current token
1169 * is not a redirection. Unparse the token and refetch it. */
1170 break;
1171 }
1172 bSuccess = TRUE;
1173
1175 }
1177 break;
1178
1179 /* Unparse the current token, and reparse it below with no separators */
1180 UnParseToken();
1181
1182 /* If bSuccess == FALSE, we know that it's still the old fetched token, but
1183 * it has been unparsed, so we need to refetch it before quitting the loop. */
1184 if (!bSuccess)
1185 {
1186 ParseToken(0, NULL);
1187 break;
1188 }
1189 }
1190#endif
1191
1192 ParseToken(0, NULL);
1193 }
1194 *Pos = _T('\0');
1195
1197 ParsedLine,
1198 ParsedLine + TailOffset);
1199 if (!Cmd)
1200 {
1201 WARN("Cannot allocate memory for Cmd!\n");
1202 ParseError();
1203 return NULL;
1204 }
1205 return Cmd;
1206}
1207
1208static PARSED_COMMAND*
1210{
1212 REDIRECTION* RedirList = NULL;
1213
1214 /* In this context, '@' is considered as a separate token */
1215 if ((*CurrentToken == _T('@')) && (CurrentTokenType == TOK_OPERATOR))
1216 {
1218 if (!Cmd)
1219 {
1220 WARN("Cannot allocate memory for Cmd!\n");
1221 ParseError();
1222 return NULL;
1223 }
1224 /* @ acts like a unary operator with low precedence,
1225 * so call the top-level parser */
1226 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
1227 return Cmd;
1228 }
1229
1230 /* Process leading redirections and get the head of the command */
1231#ifndef MSCMD_REDIR_PARSE_BUGS
1233 {
1234 if (!ParseRedirection(&RedirList))
1235 return NULL;
1236
1238 }
1239#else
1240 {
1242 while (CurrentTokenType != TOK_END)
1243 {
1244 if (!ParseRedirection(&RedirList))
1245 {
1246 /* If an actual error happened in ParseRedirection(), bail out */
1247 if (bParseError)
1248 return NULL;
1249
1250 /* Otherwise it just returned FALSE because
1251 * the current token is not a redirection. */
1252 break;
1253 }
1254 bSuccess = TRUE;
1255
1257 }
1258 if (bSuccess)
1259 {
1260 /* Unparse the current token, and reparse it with support for parenthesis */
1262 UnParseToken();
1263
1265 }
1266 }
1267#endif
1268
1270 Cmd = ParseCommandPart(&RedirList);
1272 Cmd = ParseBlock(&RedirList);
1273 else if (CurrentTokenType == TOK_END_BLOCK && !RedirList)
1274 return NULL;
1275
1276 if (Cmd)
1277 {
1278 /* FOR and IF commands cannot have leading redirection
1279 * (checked by ParseCommandPart(), errors out if so). */
1280 ASSERT(!RedirList || (Cmd->Type != C_FOR && Cmd->Type != C_IF));
1281
1282 /* Save the redirection list in the command */
1283 Cmd->Redirections = RedirList;
1284
1285 /* Return the new command */
1286 return Cmd;
1287 }
1288
1289 ParseError();
1290 FreeRedirection(RedirList);
1291 return NULL;
1292}
1293
1294static PARSED_COMMAND*
1297{
1299
1300 if (OpType == C_OP_LOWEST) // i.e. CP_MULTI
1301 {
1302 /* Ignore any parser-level comments */
1303 if (bIgnoreParserComments && (*CurrentToken == _T(':')))
1304 {
1305 /* Ignore the rest of the line, including line continuations */
1306 while (ParseToken(0, NULL) != TOK_END)
1307 ;
1308#ifdef MSCMD_PARENS_PARSE_BUGS
1309 /*
1310 * Return NULL in case we are NOT inside a parenthesized block,
1311 * otherwise continue. The effects can be observed as follows:
1312 * within a parenthesized block, every second ':'-prefixed command
1313 * is not ignored, while the first of each "pair" is ignored.
1314 * This first command **MUST NOT** be followed by an empty line,
1315 * otherwise a syntax error is raised.
1316 */
1317 if (InsideBlock == 0)
1318 {
1319#endif
1320 return NULL;
1321#ifdef MSCMD_PARENS_PARSE_BUGS
1322 }
1323 /* Get the next token */
1324 ParseToken(0, NULL);
1325#endif
1326 }
1327
1328 /*
1329 * Ignore single closing parenthesis outside of command blocks,
1330 * thus interpreted as a command. This very specific situation
1331 * can happen e.g. while running in batch mode, when jumping to
1332 * a label present inside a command block.
1333 *
1334 * NOTE: If necessary, this condition can be restricted to only
1335 * when a batch context 'bc' is active.
1336 *
1337 * NOTE 2: For further security, Windows checks that we are NOT
1338 * currently inside a parenthesized block, and also, ignores
1339 * explicitly everything (ParseToken() loop) on the same line
1340 * (including line continuations) after this closing parenthesis.
1341 *
1342 * Why doing so? Consider the following batch:
1343 *
1344 * IF 1==1 (
1345 * :label
1346 * echo A
1347 * ) ^
1348 * ELSE (
1349 * echo B
1350 * exit /b
1351 * )
1352 * GOTO :label
1353 *
1354 * First the IF block is executed. Since the condition is trivially
1355 * true, only the first block "echo A" is executed, then execution
1356 * goes after the IF block, that is, at the GOTO. Here, the GOTO
1357 * jumps within the first IF-block, however, the running context now
1358 * is NOT an IF. So parsing and execution will go through each command,
1359 * starting with 'echo A'. But then one gets the ') ^\n ELSE (' part !!
1360 * If we want to make sense of this without bailing out due to
1361 * parsing error, we should ignore this line, **including** the line
1362 * continuation. Hence we need to loop over all the tokens following
1363 * the closing parenthesis, instead of just returning NULL straight ahead.
1364 * Then execution continues with the other commands, 'echo B' and
1365 * 'exit /b' (here to stop the code loop). Execution would also
1366 * continue (if 'exit' was replaced by something else) and encounter
1367 * the lone closing parenthesis ')', that should again be ignored.
1368 *
1369 * Note that this feature has been introduced in Win2k+.
1370 */
1371 if ( (_tcscmp(CurrentToken, _T(")")) == 0) &&
1373 {
1374 ASSERT(InsideBlock == 0);
1375
1376 /* Ignore the rest of the line, including line continuations */
1377 while (ParseToken(0, NULL) != TOK_END)
1378 ;
1379 return NULL;
1380 }
1381
1382#ifdef MSCMD_PARENS_PARSE_BUGS
1383 /* Check whether we have an empty line only if we are not inside
1384 * a parenthesized block, and return NULL if so, otherwise do not
1385 * do anything; a syntax error will be raised later. */
1386 if (InsideBlock == 0)
1387#endif
1388 if (!*CurrentToken || *CurrentToken == _T('\n'))
1389 {
1391 return NULL;
1392 }
1393 }
1394
1395 if (OpType == C_OP_HIGHEST)
1396 Cmd = ParsePrimary();
1397 else
1399
1401 {
1402 PARSED_COMMAND* Left = Cmd;
1403 PARSED_COMMAND* Right;
1404
1405 Right = ParseCommandOp(OpType);
1406 if (!Right)
1407 {
1408 /*
1409 * The '&' operator is allowed to have an empty RHS.
1410 * In this case, we directly return the LHS only.
1411 * Note that Windows' CMD prefers building a '&'
1412 * command with an empty RHS.
1413 */
1414 if (!bParseError && (OpType != C_MULTI))
1415 ParseError();
1416 if (bParseError)
1417 {
1418 FreeCommand(Left);
1419 return NULL;
1420 }
1421
1422#ifndef MSCMD_MULTI_EMPTY_RHS
1423 return Left;
1424#endif
1425 }
1426
1428 if (!Cmd)
1429 {
1430 WARN("Cannot allocate memory for Cmd!\n");
1431 ParseError();
1432 FreeCommand(Left);
1433 FreeCommand(Right);
1434 return NULL;
1435 }
1436 Cmd->Subcommands = Left;
1437 Left->Next = Right;
1438#ifdef MSCMD_MULTI_EMPTY_RHS
1439 if (Right)
1440#endif
1441 Right->Next = NULL;
1442 }
1443
1444 return Cmd;
1445}
1446static __inline PARSED_COMMAND*
1449{
1450 /* Start parsing: initialize the first token */
1451
1452 /* Parse the prefix "quiet" operator '@' as a separate command.
1453 * Thus, @@foo@bar is parsed as: '@', '@', 'foo@bar'. */
1455
1457}
1458
1459
1462 IN PCTSTR Line)
1463{
1465
1466 if (Line)
1467 {
1468 if (!SubstituteVars(Line, ParseLine, _T('%')))
1469 return NULL;
1471 }
1472 else
1473 {
1474 if (!ReadLine(ParseLine, FALSE))
1475 return NULL;
1477 }
1478
1479 InitParser();
1480
1482 if (Cmd)
1483 {
1485
1486 if ((CurrentTokenType != TOK_END) &&
1487 (_tcscmp(CurrentToken, _T("\n")) != 0))
1488 {
1489 ParseError();
1490 }
1491 if (bParseError)
1492 {
1494 return NULL;
1495 }
1496
1497 /* Debugging support */
1498 if (fDumpParse)
1499 DumpCommand(Cmd, 0);
1500 }
1501 else
1502 {
1503 bIgnoreEcho = TRUE;
1504 }
1505 return Cmd;
1506}
1507
1508
1509/*
1510 * This function is similar to EchoCommand(), but is used
1511 * for dumping the command tree for debugging purposes.
1512 */
1513static VOID
1515 IN REDIRECTION* Redirections)
1516{
1517 REDIRECTION* Redir;
1518
1519 if (Redirections)
1520#ifndef MSCMD_ECHO_COMMAND_COMPAT
1521 ConOutPuts(_T(" Redir: "));
1522#else
1523 ConOutPuts(_T("Redir: "));
1524#endif
1525 for (Redir = Redirections; Redir; Redir = Redir->Next)
1526 {
1527 ConOutPrintf(_T(" %x %s%s"), Redir->Number,
1528 RedirString[Redir->Mode], Redir->Filename);
1529 }
1530}
1531
1532VOID
1535 IN ULONG SpacePad)
1536{
1537/*
1538 * This macro is like DumpCommand(Cmd, Pad);
1539 * but avoids an extra recursive level.
1540 * Note that it can be used ONLY for terminating commands!
1541 */
1542#define DUMP(Command, Pad) \
1543do { \
1544 Cmd = (Command); \
1545 SpacePad = (Pad); \
1546 goto dump; \
1547} while (0)
1548
1549 PARSED_COMMAND* Sub;
1550
1551dump:
1552 if (!Cmd)
1553 return;
1554
1555 /* Space padding */
1556 ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1557
1558 switch (Cmd->Type)
1559 {
1560 case C_COMMAND:
1561 case C_REM:
1562 {
1563 /* Generic command name, and Type */
1564#ifndef MSCMD_ECHO_COMMAND_COMPAT
1565 ConOutPrintf(_T("Cmd: %s Type: %x"),
1566 Cmd->Command.First, Cmd->Type);
1567#else
1568 ConOutPrintf(_T("Cmd: %s Type: %x "),
1569 Cmd->Command.First, Cmd->Type);
1570#endif
1571 /* Arguments */
1572 if (Cmd->Command.Rest && *(Cmd->Command.Rest))
1573#ifndef MSCMD_ECHO_COMMAND_COMPAT
1574 ConOutPrintf(_T(" Args: `%s'"), Cmd->Command.Rest);
1575#else
1576 ConOutPrintf(_T("Args: `%s' "), Cmd->Command.Rest);
1577#endif
1578 /* Redirections */
1579 DumpRedir(Cmd->Redirections);
1580
1581 ConOutChar(_T('\n'));
1582 return;
1583 }
1584
1585 case C_QUIET:
1586 {
1587#ifndef MSCMD_ECHO_COMMAND_COMPAT
1588 ConOutChar(_T('@'));
1589#else
1590 ConOutPuts(_T("@ "));
1591#endif
1592 DumpRedir(Cmd->Redirections); // FIXME: Can we have leading redirections??
1593 ConOutChar(_T('\n'));
1594
1595 /*DumpCommand*/DUMP(Cmd->Subcommands, SpacePad + 2);
1596 return;
1597 }
1598
1599 case C_BLOCK:
1600 {
1601#ifndef MSCMD_ECHO_COMMAND_COMPAT
1602 ConOutChar(_T('('));
1603#else
1604 ConOutPuts(_T("( "));
1605#endif
1606 DumpRedir(Cmd->Redirections);
1607 ConOutChar(_T('\n'));
1608
1609 SpacePad += 2;
1610
1611 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
1612 {
1613#if defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
1614 /*
1615 * We will emulate Windows' CMD handling of "CRLF" and "&" multi-command
1616 * enumeration within parenthesized command blocks.
1617 */
1618
1619 if (!Sub->Next)
1620 {
1621 DumpCommand(Sub, SpacePad);
1622 continue;
1623 }
1624
1625 if (Sub->Type != C_MULTI)
1626 {
1627 ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1628 ConOutPuts(_T("CRLF \n"));
1629 DumpCommand(Sub, SpacePad);
1630 continue;
1631 }
1632
1633 /* Now, Sub->Type == C_MULTI */
1634
1635 Cmd = Sub;
1636
1637 ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1638 ConOutPrintf(_T("%s \n"), OpString[Cmd->Type - C_OP_LOWEST]);
1639 // FIXME: Can we have redirections on these operator-type commands?
1640
1641 SpacePad += 2;
1642
1643 Cmd = Cmd->Subcommands;
1644 DumpCommand(Cmd, SpacePad);
1645 ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1646 ConOutPuts(_T("CRLF \n"));
1647 DumpCommand(Cmd->Next, SpacePad);
1648
1649 // NOTE: Next commands will remain indented.
1650
1651#else
1652
1653 /*
1654 * If this command is followed by another one, first display "CRLF".
1655 * This also emulates the CRLF placement "bug" of Windows' CMD
1656 * for the last two commands.
1657 */
1658 if (Sub->Next)
1659 {
1660 ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1661#ifndef MSCMD_ECHO_COMMAND_COMPAT
1662 ConOutPuts(_T("CRLF\n"));
1663#else
1664 ConOutPuts(_T("CRLF \n"));
1665#endif
1666 }
1667 DumpCommand(Sub, SpacePad);
1668
1669#endif // defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
1670 }
1671
1672 return;
1673 }
1674
1675 case C_MULTI:
1676 case C_OR:
1677 case C_AND:
1678 case C_PIPE:
1679 {
1680#ifndef MSCMD_ECHO_COMMAND_COMPAT
1681 ConOutPrintf(_T("%s\n"), OpString[Cmd->Type - C_OP_LOWEST]);
1682#else
1683 ConOutPrintf(_T("%s \n"), OpString[Cmd->Type - C_OP_LOWEST]);
1684#endif
1685 // FIXME: Can we have redirections on these operator-type commands?
1686
1687 SpacePad += 2;
1688
1689 Sub = Cmd->Subcommands;
1690 DumpCommand(Sub, SpacePad);
1691 /*DumpCommand*/DUMP(Sub->Next, SpacePad);
1692 return;
1693 }
1694
1695 case C_FOR:
1696 {
1697 ConOutPuts(_T("for"));
1698 /* NOTE: FOR cannot have leading redirections */
1699
1700 if (Cmd->For.Switches & FOR_DIRS) ConOutPuts(_T(" /D"));
1701 if (Cmd->For.Switches & FOR_F) ConOutPuts(_T(" /F"));
1702 if (Cmd->For.Switches & FOR_LOOP) ConOutPuts(_T(" /L"));
1703 if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPuts(_T(" /R"));
1704 if (Cmd->For.Params)
1705 ConOutPrintf(_T(" %s"), Cmd->For.Params);
1706 ConOutPrintf(_T(" %%%c in (%s) do\n"), Cmd->For.Variable, Cmd->For.List);
1707 /*DumpCommand*/DUMP(Cmd->Subcommands, SpacePad + 2);
1708 return;
1709 }
1710
1711 case C_IF:
1712 {
1713 ConOutPuts(_T("if"));
1714 /* NOTE: IF cannot have leading redirections */
1715
1716 if (Cmd->If.Flags & IFFLAG_IGNORECASE)
1717 ConOutPuts(_T(" /I"));
1718
1719 ConOutChar(_T('\n'));
1720
1721 SpacePad += 2;
1722
1723 /*
1724 * Show the IF command condition as a command.
1725 * If it is negated, indent the command more.
1726 */
1727 if (Cmd->If.Flags & IFFLAG_NEGATE)
1728 {
1729 ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1730 ConOutPuts(_T("not\n"));
1731 SpacePad += 2;
1732 }
1733
1734 ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1735
1736 /*
1737 * Command name:
1738 * - Unary operator: its name is the command name, and its argument is the command argument.
1739 * - Binary operator: its LHS is the command name, its RHS is the command argument.
1740 *
1741 * Type:
1742 * Windows' CMD (Win2k3 / Win7-10) values are as follows:
1743 * CMDEXTVERSION Type: 0x32 / 0x34
1744 * ERRORLEVEL Type: 0x33 / 0x35
1745 * DEFINED Type: 0x34 / 0x36
1746 * EXIST Type: 0x35 / 0x37
1747 * == Type: 0x37 / 0x39 (String Comparison)
1748 *
1749 * For the following command:
1750 * NOT Type: 0x36 / 0x38
1751 * Windows only prints it without any type / redirection.
1752 *
1753 * For the following command:
1754 * EQU, NEQ, etc. Type: 0x38 / 0x3a (Generic Comparison)
1755 * Windows displays it as command of unknown type.
1756 */
1757#ifndef MSCMD_ECHO_COMMAND_COMPAT
1758 ConOutPrintf(_T("Cmd: %s Type: %x"),
1759 (Cmd->If.Operator <= IF_MAX_UNARY) ?
1760 IfOperatorString[Cmd->If.Operator] :
1761 Cmd->If.LeftArg,
1762 Cmd->If.Operator);
1763#else
1764 ConOutPrintf(_T("Cmd: %s Type: %x "),
1765 (Cmd->If.Operator <= IF_MAX_UNARY) ?
1766 IfOperatorString[Cmd->If.Operator] :
1767 Cmd->If.LeftArg,
1768 Cmd->If.Operator);
1769#endif
1770 /* Arguments */
1771#ifndef MSCMD_ECHO_COMMAND_COMPAT
1772 ConOutPrintf(_T(" Args: `%s'"), Cmd->If.RightArg);
1773#else
1774 ConOutPrintf(_T("Args: `%s' "), Cmd->If.RightArg);
1775#endif
1776
1777 ConOutChar(_T('\n'));
1778
1779 if (Cmd->If.Flags & IFFLAG_NEGATE)
1780 {
1781 SpacePad -= 2;
1782 }
1783
1784 Sub = Cmd->Subcommands;
1785 DumpCommand(Sub, SpacePad);
1786 if (Sub->Next)
1787 {
1788 ConOutPrintf(_T("%*s"), SpacePad - 2, _T(""));
1789 ConOutPuts(_T("else\n"));
1790 DumpCommand(Sub->Next, SpacePad);
1791 }
1792 return;
1793 }
1794
1795 default:
1796 ConOutPrintf(_T("*** Unknown type: %x\n"), Cmd->Type);
1797 break;
1798 }
1799
1800#undef DUMP
1801}
1802
1803/*
1804 * Reconstruct a parse tree into text form; used for echoing
1805 * batch file commands and FOR instances.
1806 */
1807VOID
1810{
1811 PARSED_COMMAND* Sub;
1812 REDIRECTION* Redir;
1813
1814 if (!Cmd)
1815 return;
1816
1817 switch (Cmd->Type)
1818 {
1819 case C_COMMAND:
1820 case C_REM:
1821 {
1822 if (SubstituteForVars(Cmd->Command.First, TempBuf))
1823 ConOutPrintf(_T("%s"), TempBuf);
1824 if (SubstituteForVars(Cmd->Command.Rest, TempBuf))
1825 {
1826 ConOutPrintf(_T("%s"), TempBuf);
1827#ifdef MSCMD_ECHO_COMMAND_COMPAT
1828 /* NOTE: For Windows compatibility, add a trailing space after printing the command parameter, if present */
1829 if (*TempBuf) ConOutChar(_T(' '));
1830#endif
1831 }
1832 break;
1833 }
1834
1835 case C_QUIET:
1836 return;
1837
1838 case C_BLOCK:
1839 {
1840 BOOLEAN bIsFirstCmdCRLF;
1841
1842 ConOutChar(_T('('));
1843
1844 Sub = Cmd->Subcommands;
1845
1846 bIsFirstCmdCRLF = (Sub && Sub->Next);
1847
1848#if defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
1849 /*
1850 * We will emulate Windows' CMD handling of "CRLF" and "&" multi-command
1851 * enumeration within parenthesized command blocks.
1852 */
1853 bIsFirstCmdCRLF = bIsFirstCmdCRLF && (Sub->Type != C_MULTI);
1854#endif
1855
1856 /*
1857 * Single-command block: display all on one line.
1858 * Multi-command block: display commands on separate lines.
1859 */
1860 if (bIsFirstCmdCRLF)
1861 ConOutChar(_T('\n'));
1862
1863 for (; Sub; Sub = Sub->Next)
1864 {
1865 EchoCommand(Sub);
1866 if (Sub->Next)
1867#ifdef MSCMD_ECHO_COMMAND_COMPAT
1868 ConOutPuts(_T(" \n "));
1869#else
1870 ConOutChar(_T('\n'));
1871#endif
1872 }
1873
1874 if (bIsFirstCmdCRLF)
1875 ConOutChar(_T('\n'));
1876
1877#ifdef MSCMD_ECHO_COMMAND_COMPAT
1878 /* NOTE: For Windows compatibility, add a trailing space after printing the closing parenthesis */
1879 ConOutPuts(_T(") "));
1880#else
1881 ConOutChar(_T(')'));
1882#endif
1883 break;
1884 }
1885
1886 case C_MULTI:
1887 case C_OR:
1888 case C_AND:
1889 case C_PIPE:
1890 {
1891 Sub = Cmd->Subcommands;
1892 EchoCommand(Sub);
1893 ConOutPrintf(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
1894 EchoCommand(Sub->Next);
1895 break;
1896 }
1897
1898 case C_FOR:
1899 {
1900 ConOutPuts(_T("for"));
1901 if (Cmd->For.Switches & FOR_DIRS) ConOutPuts(_T(" /D"));
1902 if (Cmd->For.Switches & FOR_F) ConOutPuts(_T(" /F"));
1903 if (Cmd->For.Switches & FOR_LOOP) ConOutPuts(_T(" /L"));
1904 if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPuts(_T(" /R"));
1905 if (Cmd->For.Params)
1906 ConOutPrintf(_T(" %s"), Cmd->For.Params);
1907 if (Cmd->For.List && SubstituteForVars(Cmd->For.List, TempBuf))
1908 ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, TempBuf);
1909 else
1910 ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
1911 EchoCommand(Cmd->Subcommands);
1912 break;
1913 }
1914
1915 case C_IF:
1916 {
1917 ConOutPuts(_T("if"));
1918 if (Cmd->If.Flags & IFFLAG_IGNORECASE)
1919 ConOutPuts(_T(" /I"));
1920 if (Cmd->If.Flags & IFFLAG_NEGATE)
1921 ConOutPuts(_T(" not"));
1922 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, TempBuf))
1923 ConOutPrintf(_T(" %s"), TempBuf);
1924 ConOutPrintf(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
1925 if (SubstituteForVars(Cmd->If.RightArg, TempBuf))
1926 ConOutPrintf(_T(" %s "), TempBuf);
1927 Sub = Cmd->Subcommands;
1928 EchoCommand(Sub);
1929 if (Sub->Next)
1930 {
1931 ConOutPuts(_T(" else "));
1932 EchoCommand(Sub->Next);
1933 }
1934 break;
1935 }
1936
1937 default:
1938 ASSERT(FALSE);
1939 break;
1940 }
1941
1942 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
1943 {
1944 if (SubstituteForVars(Redir->Filename, TempBuf))
1945 {
1946#ifdef MSCMD_ECHO_COMMAND_COMPAT
1947 ConOutPrintf(_T("%c%s%s "),
1948 _T('0') + Redir->Number,
1949 RedirString[Redir->Mode], TempBuf);
1950#else
1951 ConOutPrintf(_T(" %c%s%s"),
1952 _T('0') + Redir->Number,
1953 RedirString[Redir->Mode], TempBuf);
1954#endif
1955 }
1956 }
1957}
1958
1959/*
1960 * "Unparse" a command into a text form suitable for passing to CMD /C.
1961 * Used for pipes. This is basically the same thing as EchoCommand(),
1962 * but writing into a string instead of to standard output.
1963 */
1964PTCHAR
1967 OUT PTCHAR Out,
1968 IN PTCHAR OutEnd)
1969{
1970/*
1971 * Since this function has the annoying requirement that it must avoid
1972 * overflowing the supplied buffer, define some helper macros to make
1973 * this less painful.
1974 */
1975#define CHAR(Char) \
1976do { \
1977 if (Out == OutEnd) return NULL; \
1978 *Out++ = Char; \
1979} while (0)
1980#define STRING(String) \
1981do { \
1982 if (Out + _tcslen(String) > OutEnd) return NULL; \
1983 Out = _stpcpy(Out, String); \
1984} while (0)
1985#define PRINTF(Format, ...) \
1986do { \
1987 UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \
1988 if (Len > (UINT)(OutEnd - Out)) return NULL; \
1989 Out += Len; \
1990} while (0)
1991#define RECURSE(Subcommand) \
1992do { \
1993 Out = UnparseCommand(Subcommand, Out, OutEnd); \
1994 if (!Out) return NULL; \
1995} while (0)
1996
1997 PARSED_COMMAND* Sub;
1998 REDIRECTION* Redir;
1999
2000 if (!Cmd)
2001 return Out;
2002
2003 switch (Cmd->Type)
2004 {
2005 case C_COMMAND:
2006 case C_REM:
2007 {
2008 /* This is fragile since there could be special characters, but
2009 * Windows doesn't bother escaping them, so for compatibility
2010 * we probably shouldn't do it either */
2011 if (!SubstituteForVars(Cmd->Command.First, TempBuf)) return NULL;
2012 STRING(TempBuf);
2013 if (!SubstituteForVars(Cmd->Command.Rest, TempBuf)) return NULL;
2014 STRING(TempBuf);
2015 break;
2016 }
2017
2018 case C_QUIET:
2019 {
2020 CHAR(_T('@'));
2021 RECURSE(Cmd->Subcommands);
2022 break;
2023 }
2024
2025 case C_BLOCK:
2026 {
2027 CHAR(_T('('));
2028 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
2029 {
2030 RECURSE(Sub);
2031 if (Sub->Next)
2032 CHAR(_T('&'));
2033 }
2034 CHAR(_T(')'));
2035 break;
2036 }
2037
2038 case C_MULTI:
2039 case C_OR:
2040 case C_AND:
2041 case C_PIPE:
2042 {
2043 Sub = Cmd->Subcommands;
2044 RECURSE(Sub);
2045 PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
2046 RECURSE(Sub->Next);
2047 break;
2048 }
2049
2050 case C_FOR:
2051 {
2052 STRING(_T("for"));
2053 if (Cmd->For.Switches & FOR_DIRS) STRING(_T(" /D"));
2054 if (Cmd->For.Switches & FOR_F) STRING(_T(" /F"));
2055 if (Cmd->For.Switches & FOR_LOOP) STRING(_T(" /L"));
2056 if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R"));
2057 if (Cmd->For.Params)
2058 PRINTF(_T(" %s"), Cmd->For.Params);
2059 if (Cmd->For.List && SubstituteForVars(Cmd->For.List, TempBuf))
2060 PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, TempBuf);
2061 else
2062 PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
2063 RECURSE(Cmd->Subcommands);
2064 break;
2065 }
2066
2067 case C_IF:
2068 {
2069 STRING(_T("if"));
2070 if (Cmd->If.Flags & IFFLAG_IGNORECASE)
2071 STRING(_T(" /I"));
2072 if (Cmd->If.Flags & IFFLAG_NEGATE)
2073 STRING(_T(" not"));
2074 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, TempBuf))
2075 PRINTF(_T(" %s"), TempBuf);
2076 PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
2077 if (!SubstituteForVars(Cmd->If.RightArg, TempBuf)) return NULL;
2078 PRINTF(_T(" %s "), TempBuf);
2079 Sub = Cmd->Subcommands;
2080 RECURSE(Sub);
2081 if (Sub->Next)
2082 {
2083 STRING(_T(" else "));
2084 RECURSE(Sub->Next);
2085 }
2086 break;
2087 }
2088
2089 default:
2090 ASSERT(FALSE);
2091 break;
2092 }
2093
2094 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
2095 {
2096 if (!SubstituteForVars(Redir->Filename, TempBuf))
2097 return NULL;
2098 PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number,
2099 RedirString[Redir->Mode], TempBuf);
2100 }
2101 return Out;
2102
2103#undef CHAR
2104#undef STRING
2105#undef PRINTF
2106#undef RECURSE
2107}
unsigned char BOOLEAN
Type
Definition: Type.h:7
#define VOID
Definition: acefi.h:82
#define CMDLINE_LENGTH
Definition: help.h:12
void restart(int argc, const char *argv[])
Definition: cmds.c:2115
BOOL SubstituteVars(IN PCTSTR Src, OUT PTSTR Dest, IN TCHAR Delim)
Definition: cmd.c:1514
BOOL ReadLine(TCHAR *commandline, BOOL bMore)
Definition: cmd.c:1721
BOOL bIgnoreEcho
Definition: cmd.c:155
BOOL SubstituteForVars(IN PCTSTR Src, OUT PTSTR Dest)
Definition: cmd.c:1596
#define IFFLAG_IGNORECASE
Definition: cmd.h:261
enum _COMMAND_TYPE COMMAND_TYPE
LPTSTR _stpcpy(LPTSTR, LPCTSTR)
Definition: misc.c:460
enum _REDIR_MODE REDIR_MODE
@ IF_CMDEXTVERSION
Definition: cmd.h:268
@ IF_EQU
Definition: cmd.h:274
#define IFFLAG_NEGATE
Definition: cmd.h:260
@ C_QUIET
Definition: cmd.h:355
@ C_OR
Definition: cmd.h:359
@ C_COMMAND
Definition: cmd.h:353
@ C_REM
Definition: cmd.h:361
@ C_AND
Definition: cmd.h:359
@ C_MULTI
Definition: cmd.h:359
@ C_IF
Definition: cmd.h:361
@ C_PIPE
Definition: cmd.h:359
@ C_FOR
Definition: cmd.h:361
#define FOR_F
Definition: cmd.h:235
#define FOR_LOOP
Definition: cmd.h:236
@ REDIR_WRITE
Definition: cmd.h:445
@ REDIR_READ
Definition: cmd.h:444
@ REDIR_APPEND
Definition: cmd.h:446
#define FOR_DIRS
Definition: cmd.h:234
#define STANDARD_SEPS
Definition: cmd.h:348
VOID FreeRedirection(REDIRECTION *)
Definition: redir.c:153
#define FOR_RECURSIVE
Definition: cmd.h:237
VOID error_syntax(PCTSTR s)
Definition: error.c:152
VOID ConOutChar(TCHAR c)
Definition: console.c:123
#define ConOutPrintf(szStr,...)
Definition: console.h:41
#define ConOutPuts(szStr)
Definition: console.h:29
static PARSED_COMMAND * ParseIf(VOID)
Definition: parser.c:780
#define DUMP(Command, Pad)
PARSED_COMMAND * ParseCommand(IN PCTSTR Line)
Definition: parser.c:1461
BOOL bHandleContinuations
Definition: parser.c:97
static __inline PARSED_COMMAND * ParseCommandOp(IN COMMAND_TYPE OpType)
Definition: parser.c:1447
static TOK_TYPE CurrentTokenType
Definition: parser.c:100
PTCHAR UnparseCommand(IN PARSED_COMMAND *Cmd, OUT PTCHAR Out, IN PTCHAR OutEnd)
Definition: parser.c:1965
BOOL bParseError
Definition: parser.c:90
static PARSED_COMMAND * ParseRem(VOID)
Definition: parser.c:1003
VOID ParseErrorEx(IN PCTSTR s)
Definition: parser.c:227
#define C_OP_HIGHEST
Definition: parser.c:39
static const TCHAR *const IfOperatorString[]
Definition: parser.c:44
static __inline BOOL IsSeparator(TCHAR Char)
Definition: parser.c:72
static __inline VOID ParseError(VOID)
Definition: parser.c:235
static PARSED_COMMAND * ParsePrimary(VOID)
Definition: parser.c:1209
#define PRINTF(Format,...)
static VOID DumpRedir(IN REDIRECTION *Redirections)
Definition: parser.c:1514
VOID EchoCommand(IN PARSED_COMMAND *Cmd)
Definition: parser.c:1808
#define CHAR(Char)
static __inline INT ParseToken(IN TCHAR ExtraEnd OPTIONAL, IN PCTSTR Separators OPTIONAL)
Definition: parser.c:450
static TOK_TYPE ParseTokenEx(IN TCHAR PrefixOperator OPTIONAL, IN TCHAR ExtraEnd OPTIONAL, IN PCTSTR Separators OPTIONAL, IN BOOL bHandleContinuations)
Definition: parser.c:241
static PARSED_COMMAND * ParseBlock(IN OUT REDIRECTION **RedirList)
Definition: parser.c:649
VOID DumpCommand(IN PARSED_COMMAND *Cmd, IN ULONG SpacePad)
Definition: parser.c:1533
static TCHAR TempBuf[CMDLINE_LENGTH]
Definition: parser.c:88
static BOOL ParseRedirection(IN OUT REDIRECTION **List)
Definition: parser.c:551
static TCHAR CurrentToken[CMDLINE_LENGTH]
Definition: parser.c:99
#define C_OP_LOWEST
Definition: parser.c:38
static PTCHAR ParsePos
Definition: parser.c:93
static VOID InitParser(VOID)
Definition: parser.c:173
static const TCHAR OpString[][3]
Definition: parser.c:40
static VOID UnParseToken(VOID)
Definition: parser.c:160
static BOOL bLineContinuations
Definition: parser.c:91
static const TCHAR RedirString[][3]
Definition: parser.c:42
static PARSED_COMMAND * ParseCommandPart(IN OUT REDIRECTION **RedirList)
Definition: parser.c:1051
BOOL bIgnoreParserComments
Definition: parser.c:96
static TCHAR ParseChar(VOID)
Definition: parser.c:187
static INT InsideBlock
Definition: parser.c:105
#define RECURSE(Subcommand)
#define IF_MAX_UNARY
static PTCHAR OldParsePos
Definition: parser.c:94
TCHAR ParseLine[CMDLINE_LENGTH]
Definition: parser.c:92
static PARSED_COMMAND * AllocCommand(IN COMMAND_TYPE Type, IN PCTSTR CmdHead OPTIONAL, IN PCTSTR CmdTail OPTIONAL)
Definition: parser.c:459
BOOLEAN fDumpParse
Definition: parser.c:36
BOOLEAN fDumpTokens
Definition: parser.c:35
enum _TOK_TYPE TOK_TYPE
#define IF_MAX_COMPARISON
#define STRING(String)
_TOK_TYPE
Definition: parser.c:78
@ TOK_REDIRECTION
Definition: parser.c:82
@ TOK_OPERATOR
Definition: parser.c:81
@ TOK_NORMAL
Definition: parser.c:80
@ TOK_END_BLOCK
Definition: parser.c:84
@ TOK_BEGIN_BLOCK
Definition: parser.c:83
@ TOK_END
Definition: parser.c:79
static PARSED_COMMAND * ParseFor(VOID)
Definition: parser.c:875
VOID FreeCommand(IN OUT PARSED_COMMAND *Cmd)
Definition: parser.c:527
static PARSED_COMMAND * ParseCommandBinaryOp(IN COMMAND_TYPE OpType)
Definition: parser.c:1295
static VOID ResetParser(IN PTCHAR Pos)
Definition: parser.c:107
#define WARN(fmt,...)
Definition: debug.h:112
#define ERR(fmt,...)
Definition: debug.h:110
static VOID StripQuotes(LPSTR in)
Definition: cmdcons.c:116
#define cmd_free(ptr)
Definition: cmddbg.h:31
#define cmd_alloc(size)
Definition: cmddbg.h:29
#define cmd_dup(str)
Definition: cmddbg.h:32
ush Pos
Definition: deflate.h:92
@ Out
#define NULL
Definition: types.h:112
#define TRUE
Definition: types.h:120
#define FALSE
Definition: types.h:117
#define INVALID_HANDLE_VALUE
Definition: compat.h:731
static BOOLEAN bSuccess
Definition: drive.cpp:433
IN PVCB IN PBCB OUT PDIRENT IN USHORT IN POEM_STRING Filename
Definition: fatprocs.h:939
unsigned int BOOL
Definition: ntddk_ex.h:94
GLdouble s
Definition: gl.h:2039
#define _istspace
Definition: tchar.h:1504
#define _tcscmp
Definition: tchar.h:1424
#define _istdigit
Definition: tchar.h:1494
#define _tcscpy
Definition: tchar.h:623
#define _tcschr
Definition: tchar.h:1406
#define error(str)
Definition: mkdosfs.c:1605
#define memcpy(s1, s2, n)
Definition: mkisofs.h:878
#define ASSERT(a)
Definition: mode.c:44
BOOL bEnableExtensions
Definition: more.c:53
static void dump(const void *ptr, unsigned len)
Definition: msc.c:95
LPCSTR PCTSTR
Definition: ntbasedef.h:488
char * PTCHAR
Definition: ntbasedef.h:476
_In_opt_ PENTER_STATE_SYSTEM_HANDLER _In_opt_ PVOID _In_ LONG _In_opt_ LONG volatile * Number
Definition: ntpoapi.h:207
void(* Func)(int)
#define C_BLOCK
Definition: rsym.h:130
@ Cmd
Definition: sacdrv.h:278
#define memset(x, y, z)
Definition: compat.h:39
OpType
Definition: simd.h:223
PULONG MinorVersion OPTIONAL
Definition: CrossNt.h:68
Definition: shell.h:41
Definition: ncftp.h:79
COMMAND_TYPE Type
Definition: cmd.h:374
struct _PARSED_COMMAND * Next
Definition: cmd.h:372
REDIR_MODE Mode
Definition: cmd.h:453
HANDLE OldHandle
Definition: cmd.h:451
BYTE Number
Definition: cmd.h:452
TCHAR Filename[]
Definition: cmd.h:454
struct _REDIRECTION * Next
Definition: cmd.h:450
#define FIELD_OFFSET(t, f)
Definition: typedefs.h:255
ULONG_PTR SIZE_T
Definition: typedefs.h:80
int32_t INT
Definition: typedefs.h:58
#define IN
Definition: typedefs.h:39
uint32_t ULONG
Definition: typedefs.h:59
#define OUT
Definition: typedefs.h:40
#define _T(x)
Definition: vfdio.h:22
_Must_inspect_result_ _In_ WDFCMRESLIST List
Definition: wdfresource.h:550
char TCHAR
Definition: xmlstorage.h:189
CHAR * PTSTR
Definition: xmlstorage.h:191
#define _tcsnicmp
Definition: xmlstorage.h:207
#define _tcsstr
Definition: xmlstorage.h:199
#define _tcslen
Definition: xmlstorage.h:198
#define _tcsicmp
Definition: xmlstorage.h:205
unsigned char BYTE
Definition: xxhash.c:193