xref: /reactos/base/shell/cmd/parser.c (revision 7eead935)
1 /*
2  *  PARSER.C - command parsing.
3  */
4 
5 #include "precomp.h"
6 
7 #define C_OP_LOWEST C_MULTI
8 #define C_OP_HIGHEST C_PIPE
9 static const TCHAR OpString[][3] = { _T("&"), _T("||"), _T("&&"), _T("|") };
10 
11 static const TCHAR RedirString[][3] = { _T("<"), _T(">"), _T(">>") };
12 
13 static const TCHAR *const IfOperatorString[] =
14 {
15     _T("cmdextversion"),
16     _T("defined"),
17     _T("errorlevel"),
18     _T("exist"),
19 #define IF_MAX_UNARY IF_EXIST
20     _T("=="),
21     _T("equ"),
22     _T("gtr"),
23     _T("geq"),
24     _T("lss"),
25     _T("leq"),
26     _T("neq"),
27 #define IF_MAX_COMPARISON IF_NEQ
28 };
29 
30 /* These three characters act like spaces to the parser in most contexts */
31 #define STANDARD_SEPS _T(",;=")
32 
33 static BOOL IsSeparator(TCHAR Char)
34 {
35     return _istspace(Char) || (Char && _tcschr(STANDARD_SEPS, Char));
36 }
37 
38 enum
39 {
40     TOK_END,
41     TOK_NORMAL,
42     TOK_OPERATOR,
43     TOK_REDIRECTION,
44     TOK_BEGIN_BLOCK,
45     TOK_END_BLOCK
46 };
47 
48 static BOOL bParseError;
49 static BOOL bLineContinuations;
50 static TCHAR ParseLine[CMDLINE_LENGTH];
51 static TCHAR *ParsePos;
52 static TCHAR CurChar;
53 
54 static TCHAR CurrentToken[CMDLINE_LENGTH];
55 static int CurrentTokenType;
56 static int InsideBlock;
57 
58 static TCHAR ParseChar(void)
59 {
60     TCHAR Char;
61 
62     if (bParseError)
63         return (CurChar = 0);
64 
65 restart:
66     /*
67      * Although CRs can be injected into a line via an environment
68      * variable substitution, the parser ignores them - they won't
69      * even separate tokens.
70      */
71     do
72     {
73         Char = *ParsePos++;
74     }
75     while (Char == _T('\r'));
76 
77     if (!Char)
78     {
79         ParsePos--;
80         if (bLineContinuations)
81         {
82             if (!ReadLine(ParseLine, TRUE))
83             {
84                 /* ^C pressed, or line was too long */
85                 bParseError = TRUE;
86             }
87             else if (*(ParsePos = ParseLine))
88             {
89                 goto restart;
90             }
91         }
92     }
93     return (CurChar = Char);
94 }
95 
96 static void ParseError(void)
97 {
98     error_syntax(CurrentTokenType != TOK_END ? CurrentToken : NULL);
99     bParseError = TRUE;
100 }
101 
102 /*
103  * Yes, cmd has a Lexical Analyzer. Whenever the parser gives an "xxx was
104  * unexpected at this time." message, it shows what the last token read was.
105  */
106 static int ParseToken(TCHAR ExtraEnd, TCHAR *Separators)
107 {
108     TCHAR *Out = CurrentToken;
109     TCHAR Char;
110     int Type;
111     BOOL bInQuote = FALSE;
112 
113     for (Char = CurChar; Char && Char != _T('\n'); Char = ParseChar())
114     {
115         bInQuote ^= (Char == _T('"'));
116         if (!bInQuote)
117         {
118             if (Separators != NULL)
119             {
120                 if (_istspace(Char) || _tcschr(Separators, Char))
121                 {
122                     /* Skip leading separators */
123                     if (Out == CurrentToken)
124                         continue;
125                     break;
126                 }
127             }
128 
129             /* Check for numbered redirection */
130             if ((Char >= _T('0') && Char <= _T('9') &&
131                    (ParsePos == &ParseLine[1] || IsSeparator(ParsePos[-2]))
132                    && (*ParsePos == _T('<') || *ParsePos == _T('>'))))
133             {
134                 break;
135             }
136 
137             if (Char == ExtraEnd)
138                 break;
139             if (InsideBlock && Char == _T(')'))
140                 break;
141             if (_tcschr(_T("&|<>"), Char))
142                 break;
143 
144             if (Char == _T('^'))
145             {
146                 Char = ParseChar();
147                 /* Eat up a \n, allowing line continuation */
148                 if (Char == _T('\n'))
149                     Char = ParseChar();
150                 /* Next character is a forced literal */
151             }
152         }
153         if (Out == &CurrentToken[CMDLINE_LENGTH - 1])
154             break;
155         *Out++ = Char;
156     }
157 
158     /* Check if we got at least one character before reaching a special one.
159      * If so, return them and leave the special for the next call. */
160     if (Out != CurrentToken)
161     {
162         Type = TOK_NORMAL;
163     }
164     else if (Char == _T('('))
165     {
166         Type = TOK_BEGIN_BLOCK;
167         *Out++ = Char;
168         ParseChar();
169     }
170     else if (Char == _T(')'))
171     {
172         Type = TOK_END_BLOCK;
173         *Out++ = Char;
174         ParseChar();
175     }
176     else if (Char == _T('&') || Char == _T('|'))
177     {
178         Type = TOK_OPERATOR;
179         *Out++ = Char;
180         Char = ParseChar();
181         /* check for && or || */
182         if (Char == Out[-1])
183         {
184             *Out++ = Char;
185             ParseChar();
186         }
187     }
188     else if ((Char >= _T('0') && Char <= _T('9'))
189              || (Char == _T('<') || Char == _T('>')))
190     {
191         Type = TOK_REDIRECTION;
192         if (Char >= _T('0') && Char <= _T('9'))
193         {
194             *Out++ = Char;
195             Char = ParseChar();
196         }
197         *Out++ = Char;
198         Char = ParseChar();
199         if (Char == Out[-1])
200         {
201             /* Strangely, the tokenizer allows << as well as >>... (it
202              * will cause an error when trying to parse it though) */
203             *Out++ = Char;
204             Char = ParseChar();
205         }
206         if (Char == _T('&'))
207         {
208             *Out++ = Char;
209             while (IsSeparator(Char = ParseChar()))
210                 ;
211             if (Char >= _T('0') && Char <= _T('9'))
212             {
213                 *Out++ = Char;
214                 ParseChar();
215             }
216         }
217     }
218     else
219     {
220         Type = TOK_END;
221     }
222     *Out = _T('\0');
223     return CurrentTokenType = Type;
224 }
225 
226 static BOOL ParseRedirection(REDIRECTION **List)
227 {
228     TCHAR *Tok = CurrentToken;
229     BYTE Number;
230     REDIR_MODE RedirMode;
231     REDIRECTION *Redir;
232 
233     if (*Tok >= _T('0') && *Tok <= _T('9'))
234         Number = *Tok++ - _T('0');
235     else
236         Number = *Tok == _T('<') ? 0 : 1;
237 
238     if (*Tok++ == _T('<'))
239     {
240         RedirMode = REDIR_READ;
241         if (*Tok == _T('<'))
242             goto fail;
243     }
244     else
245     {
246         RedirMode = REDIR_WRITE;
247         if (*Tok == _T('>'))
248         {
249             RedirMode = REDIR_APPEND;
250             Tok++;
251         }
252     }
253 
254     if (!*Tok)
255     {
256         /* The file name was not part of this token, so it'll be the next one */
257         if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
258             goto fail;
259         Tok = CurrentToken;
260     }
261 
262     /* If a redirection for this handle number already exists, delete it */
263     while ((Redir = *List))
264     {
265         if (Redir->Number == Number)
266         {
267             *List = Redir->Next;
268             cmd_free(Redir);
269             continue;
270         }
271         List = &Redir->Next;
272     }
273 
274     Redir = cmd_alloc(FIELD_OFFSET(REDIRECTION, Filename[_tcslen(Tok) + 1]));
275     if (!Redir)
276     {
277         WARN("Cannot allocate memory for Redir!\n");
278         goto fail;
279     }
280     Redir->Next = NULL;
281     Redir->OldHandle = INVALID_HANDLE_VALUE;
282     Redir->Number = Number;
283     Redir->Mode = RedirMode;
284     _tcscpy(Redir->Filename, Tok);
285     *List = Redir;
286     return TRUE;
287 
288 fail:
289     ParseError();
290     FreeRedirection(*List);
291     *List = NULL;
292     return FALSE;
293 }
294 
295 static PARSED_COMMAND *ParseCommandOp(int OpType);
296 
297 /* Parse a parenthesized block */
298 static PARSED_COMMAND *ParseBlock(REDIRECTION *RedirList)
299 {
300     PARSED_COMMAND *Cmd, *Sub, **NextPtr;
301 
302     Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
303     if (!Cmd)
304     {
305         WARN("Cannot allocate memory for Cmd!\n");
306         ParseError();
307         FreeRedirection(RedirList);
308         return NULL;
309     }
310     Cmd->Type = C_BLOCK;
311     Cmd->Next = NULL;
312     Cmd->Subcommands = NULL;
313     Cmd->Redirections = RedirList;
314 
315     /* Read the block contents */
316     NextPtr = &Cmd->Subcommands;
317     InsideBlock++;
318     while (1)
319     {
320         Sub = ParseCommandOp(C_OP_LOWEST);
321         if (Sub)
322         {
323             *NextPtr = Sub;
324             NextPtr = &Sub->Next;
325         }
326         else if (bParseError)
327         {
328             InsideBlock--;
329             FreeCommand(Cmd);
330             return NULL;
331         }
332 
333         if (CurrentTokenType == TOK_END_BLOCK)
334             break;
335 
336         /* Skip past the \n */
337         ParseChar();
338     }
339     InsideBlock--;
340 
341     /* Process any trailing redirections */
342     while (ParseToken(0, STANDARD_SEPS) == TOK_REDIRECTION)
343     {
344         if (!ParseRedirection(&Cmd->Redirections))
345         {
346             FreeCommand(Cmd);
347             return NULL;
348         }
349     }
350     return Cmd;
351 }
352 
353 /* Parse an IF statement */
354 static PARSED_COMMAND *ParseIf(void)
355 {
356     int Type;
357     PARSED_COMMAND *Cmd;
358 
359     Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
360     if (!Cmd)
361     {
362         WARN("Cannot allocate memory for Cmd!\n");
363         ParseError();
364         return NULL;
365     }
366     memset(Cmd, 0, sizeof(PARSED_COMMAND));
367     Cmd->Type = C_IF;
368 
369     Type = CurrentTokenType;
370     if (_tcsicmp(CurrentToken, _T("/I")) == 0)
371     {
372         Cmd->If.Flags |= IFFLAG_IGNORECASE;
373         Type = ParseToken(0, STANDARD_SEPS);
374     }
375     if (_tcsicmp(CurrentToken, _T("not")) == 0)
376     {
377         Cmd->If.Flags |= IFFLAG_NEGATE;
378         Type = ParseToken(0, STANDARD_SEPS);
379     }
380 
381     if (Type != TOK_NORMAL)
382     {
383         FreeCommand(Cmd);
384         ParseError();
385         return NULL;
386     }
387 
388     /* Check for unary operators */
389     for (; Cmd->If.Operator <= IF_MAX_UNARY; Cmd->If.Operator++)
390     {
391         if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
392         {
393             if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
394             {
395                 FreeCommand(Cmd);
396                 ParseError();
397                 return NULL;
398             }
399             Cmd->If.RightArg = cmd_dup(CurrentToken);
400             goto condition_done;
401         }
402     }
403 
404     /* It must be a two-argument (comparison) operator. It could be ==, so
405      * the equals sign can't be treated as whitespace here. */
406     Cmd->If.LeftArg = cmd_dup(CurrentToken);
407     ParseToken(0, _T(",;"));
408 
409     /* The right argument can come immediately after == */
410     if (_tcsnicmp(CurrentToken, _T("=="), 2) == 0 && CurrentToken[2])
411     {
412         Cmd->If.RightArg = cmd_dup(&CurrentToken[2]);
413         goto condition_done;
414     }
415 
416     for (; Cmd->If.Operator <= IF_MAX_COMPARISON; Cmd->If.Operator++)
417     {
418         if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
419         {
420             if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
421                 break;
422             Cmd->If.RightArg = cmd_dup(CurrentToken);
423             goto condition_done;
424         }
425     }
426     FreeCommand(Cmd);
427     ParseError();
428     return NULL;
429 
430 condition_done:
431     Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
432     if (Cmd->Subcommands == NULL)
433     {
434         FreeCommand(Cmd);
435         return NULL;
436     }
437     if (_tcsicmp(CurrentToken, _T("else")) == 0)
438     {
439         Cmd->Subcommands->Next = ParseCommandOp(C_OP_LOWEST);
440         if (Cmd->Subcommands->Next == NULL)
441         {
442             FreeCommand(Cmd);
443             return NULL;
444         }
445     }
446 
447     return Cmd;
448 }
449 
450 /*
451  * Parse a FOR command.
452  * Syntax is: FOR [options] %var IN (list) DO command
453  */
454 static PARSED_COMMAND *ParseFor(void)
455 {
456     PARSED_COMMAND *Cmd;
457     TCHAR List[CMDLINE_LENGTH];
458     TCHAR *Pos = List;
459 
460     Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
461     if (!Cmd)
462     {
463         WARN("Cannot allocate memory for Cmd!\n");
464         ParseError();
465         return NULL;
466     }
467     memset(Cmd, 0, sizeof(PARSED_COMMAND));
468     Cmd->Type = C_FOR;
469 
470     while (1)
471     {
472         if (_tcsicmp(CurrentToken, _T("/D")) == 0)
473             Cmd->For.Switches |= FOR_DIRS;
474         else if (_tcsicmp(CurrentToken, _T("/F")) == 0)
475         {
476             Cmd->For.Switches |= FOR_F;
477             if (!Cmd->For.Params)
478             {
479                 ParseToken(0, STANDARD_SEPS);
480                 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
481                     break;
482                 Cmd->For.Params = cmd_dup(CurrentToken);
483             }
484         }
485         else if (_tcsicmp(CurrentToken, _T("/L")) == 0)
486             Cmd->For.Switches |= FOR_LOOP;
487         else if (_tcsicmp(CurrentToken, _T("/R")) == 0)
488         {
489             Cmd->For.Switches |= FOR_RECURSIVE;
490             if (!Cmd->For.Params)
491             {
492                 ParseToken(0, STANDARD_SEPS);
493                 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
494                     break;
495                 StripQuotes(CurrentToken);
496                 Cmd->For.Params = cmd_dup(CurrentToken);
497             }
498         }
499         else
500             break;
501         ParseToken(0, STANDARD_SEPS);
502     }
503 
504     /* Make sure there aren't two different switches specified
505      * at the same time, unless they're /D and /R */
506     if ((Cmd->For.Switches & (Cmd->For.Switches - 1)) != 0
507         && Cmd->For.Switches != (FOR_DIRS | FOR_RECURSIVE))
508     {
509         goto error;
510     }
511 
512     /* Variable name should be % and just one other character */
513     if (CurrentToken[0] != _T('%') || _tcslen(CurrentToken) != 2)
514         goto error;
515     Cmd->For.Variable = CurrentToken[1];
516 
517     ParseToken(0, STANDARD_SEPS);
518     if (_tcsicmp(CurrentToken, _T("in")) != 0)
519         goto error;
520 
521     if (ParseToken(_T('('), STANDARD_SEPS) != TOK_BEGIN_BLOCK)
522         goto error;
523 
524     while (1)
525     {
526         int Type;
527 
528         /* Pretend we're inside a block so the tokenizer will stop on ')' */
529         InsideBlock++;
530         Type = ParseToken(0, STANDARD_SEPS);
531         InsideBlock--;
532 
533         if (Type == TOK_END_BLOCK)
534             break;
535 
536         if (Type == TOK_END)
537         {
538             /* Skip past the \n */
539             ParseChar();
540             continue;
541         }
542 
543         if (Type != TOK_NORMAL)
544             goto error;
545 
546         if (Pos != List)
547             *Pos++ = _T(' ');
548 
549         if (Pos + _tcslen(CurrentToken) >= &List[CMDLINE_LENGTH])
550             goto error;
551         Pos = _stpcpy(Pos, CurrentToken);
552     }
553     *Pos = _T('\0');
554     Cmd->For.List = cmd_dup(List);
555 
556     ParseToken(0, STANDARD_SEPS);
557     if (_tcsicmp(CurrentToken, _T("do")) != 0)
558         goto error;
559 
560     Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
561     if (Cmd->Subcommands == NULL)
562     {
563         FreeCommand(Cmd);
564         return NULL;
565     }
566 
567     return Cmd;
568 
569 error:
570     FreeCommand(Cmd);
571     ParseError();
572     return NULL;
573 }
574 
575 /* Parse a REM command */
576 static PARSED_COMMAND *ParseRem(void)
577 {
578     /* Just ignore the rest of the line */
579     while (CurChar && CurChar != _T('\n'))
580         ParseChar();
581     return NULL;
582 }
583 
584 static DECLSPEC_NOINLINE PARSED_COMMAND *ParseCommandPart(REDIRECTION *RedirList)
585 {
586     TCHAR ParsedLine[CMDLINE_LENGTH];
587     PARSED_COMMAND *Cmd;
588     PARSED_COMMAND *(*Func)(void);
589 
590     TCHAR *Pos = _stpcpy(ParsedLine, CurrentToken) + 1;
591     DWORD_PTR TailOffset = Pos - ParsedLine;
592 
593     /* Check for special forms */
594     if ((Func = ParseFor, _tcsicmp(ParsedLine, _T("for")) == 0) ||
595         (Func = ParseIf,  _tcsicmp(ParsedLine, _T("if")) == 0)  ||
596         (Func = ParseRem, _tcsicmp(ParsedLine, _T("rem")) == 0))
597     {
598         ParseToken(0, STANDARD_SEPS);
599         /* Do special parsing only if it's not followed by /? */
600         if (_tcscmp(CurrentToken, _T("/?")) != 0)
601         {
602             if (RedirList)
603             {
604                 ParseError();
605                 FreeRedirection(RedirList);
606                 return NULL;
607             }
608             return Func();
609         }
610         Pos = _stpcpy(Pos, _T(" /?"));
611     }
612 
613     /* Now get the tail */
614     while (1)
615     {
616         int Type = ParseToken(0, NULL);
617         if (Type == TOK_NORMAL)
618         {
619             if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
620             {
621                 ParseError();
622                 FreeRedirection(RedirList);
623                 return NULL;
624             }
625             Pos = _stpcpy(Pos, CurrentToken);
626         }
627         else if (Type == TOK_REDIRECTION)
628         {
629             if (!ParseRedirection(&RedirList))
630                 return NULL;
631         }
632         else
633         {
634             break;
635         }
636     }
637     *Pos++ = _T('\0');
638 
639     Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.First[Pos - ParsedLine]));
640     if (!Cmd)
641     {
642         WARN("Cannot allocate memory for Cmd!\n");
643         ParseError();
644         FreeRedirection(RedirList);
645         return NULL;
646     }
647     Cmd->Type = C_COMMAND;
648     Cmd->Next = NULL;
649     Cmd->Subcommands = NULL;
650     Cmd->Redirections = RedirList;
651     memcpy(Cmd->Command.First, ParsedLine, (Pos - ParsedLine) * sizeof(TCHAR));
652     Cmd->Command.Rest = Cmd->Command.First + TailOffset;
653     return Cmd;
654 }
655 
656 static PARSED_COMMAND *ParsePrimary(void)
657 {
658     REDIRECTION *RedirList = NULL;
659     int Type;
660 
661     while (IsSeparator(CurChar))
662     {
663         if (CurChar == _T('\n'))
664             return NULL;
665         ParseChar();
666     }
667 
668     if (!CurChar)
669         return NULL;
670 
671     if (CurChar == _T(':'))
672     {
673         /* "Ignore" the rest of the line.
674          * (Line continuations will still be parsed, though.) */
675         while (ParseToken(0, NULL) != TOK_END)
676             ;
677         return NULL;
678     }
679 
680     if (CurChar == _T('@'))
681     {
682         PARSED_COMMAND *Cmd;
683         ParseChar();
684         Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
685         if (!Cmd)
686         {
687             WARN("Cannot allocate memory for Cmd!\n");
688             ParseError();
689             return NULL;
690         }
691         Cmd->Type = C_QUIET;
692         Cmd->Next = NULL;
693         /* @ acts like a unary operator with low precedence,
694          * so call the top-level parser */
695         Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
696         Cmd->Redirections = NULL;
697         return Cmd;
698     }
699 
700     /* Process leading redirections and get the head of the command */
701     while ((Type = ParseToken(_T('('), STANDARD_SEPS)) == TOK_REDIRECTION)
702     {
703         if (!ParseRedirection(&RedirList))
704             return NULL;
705     }
706 
707     if (Type == TOK_NORMAL)
708         return ParseCommandPart(RedirList);
709     else if (Type == TOK_BEGIN_BLOCK)
710         return ParseBlock(RedirList);
711     else if (Type == TOK_END_BLOCK && !RedirList)
712         return NULL;
713 
714     ParseError();
715     FreeRedirection(RedirList);
716     return NULL;
717 }
718 
719 static PARSED_COMMAND *ParseCommandOp(int OpType)
720 {
721     PARSED_COMMAND *Cmd, *Left, *Right;
722 
723     if (OpType == C_OP_HIGHEST)
724         Cmd = ParsePrimary();
725     else
726         Cmd = ParseCommandOp(OpType + 1);
727 
728     if (Cmd && !_tcscmp(CurrentToken, OpString[OpType - C_OP_LOWEST]))
729     {
730         Left = Cmd;
731         Right = ParseCommandOp(OpType);
732         if (!Right)
733         {
734             if (!bParseError)
735             {
736                 /* & is allowed to have an empty RHS */
737                 if (OpType == C_MULTI)
738                     return Left;
739                 ParseError();
740             }
741             FreeCommand(Left);
742             return NULL;
743         }
744 
745         Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
746         if (!Cmd)
747         {
748             WARN("Cannot allocate memory for Cmd!\n");
749             ParseError();
750             FreeCommand(Left);
751             FreeCommand(Right);
752             return NULL;
753         }
754         Cmd->Type = OpType;
755         Cmd->Next = NULL;
756         Cmd->Redirections = NULL;
757         Cmd->Subcommands = Left;
758         Left->Next = Right;
759         Right->Next = NULL;
760     }
761 
762     return Cmd;
763 }
764 
765 PARSED_COMMAND *
766 ParseCommand(LPTSTR Line)
767 {
768     PARSED_COMMAND *Cmd;
769 
770     if (Line)
771     {
772         if (!SubstituteVars(Line, ParseLine, _T('%')))
773             return NULL;
774         bLineContinuations = FALSE;
775     }
776     else
777     {
778         if (!ReadLine(ParseLine, FALSE))
779             return NULL;
780         bLineContinuations = TRUE;
781     }
782     bParseError = FALSE;
783     ParsePos = ParseLine;
784     CurChar = _T(' ');
785 
786     Cmd = ParseCommandOp(C_OP_LOWEST);
787     if (Cmd)
788     {
789         if (CurrentTokenType != TOK_END)
790             ParseError();
791         if (bParseError)
792         {
793             FreeCommand(Cmd);
794             Cmd = NULL;
795         }
796         bIgnoreEcho = FALSE;
797     }
798     else
799     {
800         bIgnoreEcho = TRUE;
801     }
802     return Cmd;
803 }
804 
805 
806 /*
807  * Reconstruct a parse tree into text form; used for echoing
808  * batch file commands and FOR instances.
809  */
810 VOID
811 EchoCommand(PARSED_COMMAND *Cmd)
812 {
813     TCHAR Buf[CMDLINE_LENGTH];
814     PARSED_COMMAND *Sub;
815     REDIRECTION *Redir;
816 
817     switch (Cmd->Type)
818     {
819     case C_COMMAND:
820         if (SubstituteForVars(Cmd->Command.First, Buf))
821             ConOutPrintf(_T("%s"), Buf);
822         if (SubstituteForVars(Cmd->Command.Rest, Buf))
823             ConOutPrintf(_T("%s"), Buf);
824         break;
825     case C_QUIET:
826         return;
827     case C_BLOCK:
828         ConOutChar(_T('('));
829         Sub = Cmd->Subcommands;
830         if (Sub && !Sub->Next)
831         {
832             /* Single-command block: display all on one line */
833             EchoCommand(Sub);
834         }
835         else if (Sub)
836         {
837             /* Multi-command block: display parenthesis on separate lines */
838             ConOutChar(_T('\n'));
839             do
840             {
841                 EchoCommand(Sub);
842                 ConOutChar(_T('\n'));
843                 Sub = Sub->Next;
844             } while (Sub);
845         }
846         ConOutChar(_T(')'));
847         break;
848     case C_MULTI:
849     case C_IFFAILURE:
850     case C_IFSUCCESS:
851     case C_PIPE:
852         Sub = Cmd->Subcommands;
853         EchoCommand(Sub);
854         ConOutPrintf(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
855         EchoCommand(Sub->Next);
856         break;
857     case C_IF:
858         ConOutPrintf(_T("if"));
859         if (Cmd->If.Flags & IFFLAG_IGNORECASE)
860             ConOutPrintf(_T(" /I"));
861         if (Cmd->If.Flags & IFFLAG_NEGATE)
862             ConOutPrintf(_T(" not"));
863         if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf))
864             ConOutPrintf(_T(" %s"), Buf);
865         ConOutPrintf(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
866         if (SubstituteForVars(Cmd->If.RightArg, Buf))
867             ConOutPrintf(_T(" %s "), Buf);
868         Sub = Cmd->Subcommands;
869         EchoCommand(Sub);
870         if (Sub->Next)
871         {
872             ConOutPrintf(_T(" else "));
873             EchoCommand(Sub->Next);
874         }
875         break;
876     case C_FOR:
877         ConOutPrintf(_T("for"));
878         if (Cmd->For.Switches & FOR_DIRS)      ConOutPrintf(_T(" /D"));
879         if (Cmd->For.Switches & FOR_F)         ConOutPrintf(_T(" /F"));
880         if (Cmd->For.Switches & FOR_LOOP)      ConOutPrintf(_T(" /L"));
881         if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPrintf(_T(" /R"));
882         if (Cmd->For.Params)
883             ConOutPrintf(_T(" %s"), Cmd->For.Params);
884         ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
885         EchoCommand(Cmd->Subcommands);
886         break;
887     }
888 
889     for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
890     {
891         if (SubstituteForVars(Redir->Filename, Buf))
892         {
893             ConOutPrintf(_T(" %c%s%s"), _T('0') + Redir->Number,
894                          RedirString[Redir->Mode], Buf);
895         }
896     }
897 }
898 
899 /*
900  * "Unparse" a command into a text form suitable for passing to CMD /C.
901  * Used for pipes. This is basically the same thing as EchoCommand, but
902  * writing into a string instead of to standard output.
903  */
904 TCHAR *
905 Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd)
906 {
907     TCHAR Buf[CMDLINE_LENGTH];
908     PARSED_COMMAND *Sub;
909     REDIRECTION *Redir;
910 
911 /*
912  * Since this function has the annoying requirement that it must avoid
913  * overflowing the supplied buffer, define some helper macros to make
914  * this less painful.
915  */
916 #define CHAR(Char) \
917 do { \
918     if (Out == OutEnd) return NULL; \
919     *Out++ = Char; \
920 } while (0)
921 #define STRING(String) \
922 do { \
923     if (Out + _tcslen(String) > OutEnd) return NULL; \
924     Out = _stpcpy(Out, String); \
925 } while (0)
926 #define PRINTF(Format, ...) \
927 do { \
928     UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \
929     if (Len > (UINT)(OutEnd - Out)) return NULL; \
930     Out += Len; \
931 } while (0)
932 #define RECURSE(Subcommand) \
933 do { \
934     Out = Unparse(Subcommand, Out, OutEnd); \
935     if (!Out) return NULL; \
936 } while (0)
937 
938     switch (Cmd->Type)
939     {
940     case C_COMMAND:
941         /* This is fragile since there could be special characters, but
942          * Windows doesn't bother escaping them, so for compatibility
943          * we probably shouldn't do it either */
944         if (!SubstituteForVars(Cmd->Command.First, Buf)) return NULL;
945         STRING(Buf);
946         if (!SubstituteForVars(Cmd->Command.Rest, Buf)) return NULL;
947         STRING(Buf);
948         break;
949     case C_QUIET:
950         CHAR(_T('@'));
951         RECURSE(Cmd->Subcommands);
952         break;
953     case C_BLOCK:
954         CHAR(_T('('));
955         for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
956         {
957             RECURSE(Sub);
958             if (Sub->Next)
959                 CHAR(_T('&'));
960         }
961         CHAR(_T(')'));
962         break;
963     case C_MULTI:
964     case C_IFFAILURE:
965     case C_IFSUCCESS:
966     case C_PIPE:
967         Sub = Cmd->Subcommands;
968         RECURSE(Sub);
969         PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
970         RECURSE(Sub->Next);
971         break;
972     case C_IF:
973         STRING(_T("if"));
974         if (Cmd->If.Flags & IFFLAG_IGNORECASE)
975             STRING(_T(" /I"));
976         if (Cmd->If.Flags & IFFLAG_NEGATE)
977             STRING(_T(" not"));
978         if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf))
979             PRINTF(_T(" %s"), Buf);
980         PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
981         if (!SubstituteForVars(Cmd->If.RightArg, Buf)) return NULL;
982         PRINTF(_T(" %s "), Buf);
983         Sub = Cmd->Subcommands;
984         RECURSE(Sub);
985         if (Sub->Next)
986         {
987             STRING(_T(" else "));
988             RECURSE(Sub->Next);
989         }
990         break;
991     case C_FOR:
992         STRING(_T("for"));
993         if (Cmd->For.Switches & FOR_DIRS)      STRING(_T(" /D"));
994         if (Cmd->For.Switches & FOR_F)         STRING(_T(" /F"));
995         if (Cmd->For.Switches & FOR_LOOP)      STRING(_T(" /L"));
996         if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R"));
997         if (Cmd->For.Params)
998             PRINTF(_T(" %s"), Cmd->For.Params);
999         PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
1000         RECURSE(Cmd->Subcommands);
1001         break;
1002     }
1003 
1004     for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
1005     {
1006         if (!SubstituteForVars(Redir->Filename, Buf))
1007             return NULL;
1008         PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number,
1009                RedirString[Redir->Mode], Buf);
1010     }
1011     return Out;
1012 }
1013 
1014 VOID
1015 FreeCommand(PARSED_COMMAND *Cmd)
1016 {
1017     if (Cmd->Subcommands)
1018         FreeCommand(Cmd->Subcommands);
1019     if (Cmd->Next)
1020         FreeCommand(Cmd->Next);
1021     FreeRedirection(Cmd->Redirections);
1022     if (Cmd->Type == C_IF)
1023     {
1024         cmd_free(Cmd->If.LeftArg);
1025         cmd_free(Cmd->If.RightArg);
1026     }
1027     else if (Cmd->Type == C_FOR)
1028     {
1029         cmd_free(Cmd->For.Params);
1030         cmd_free(Cmd->For.List);
1031     }
1032     cmd_free(Cmd);
1033 }
1034