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