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