[FLOPPY] Don't wait forever while trying to determine media type.
[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(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 Redir->Next = NULL;
276 Redir->OldHandle = INVALID_HANDLE_VALUE;
277 Redir->Number = Number;
278 Redir->Mode = RedirMode;
279 _tcscpy(Redir->Filename, Tok);
280 *List = Redir;
281 return TRUE;
282
283 fail:
284 ParseError();
285 FreeRedirection(*List);
286 *List = NULL;
287 return FALSE;
288 }
289
290 static PARSED_COMMAND *ParseCommandOp(int OpType);
291
292 /* Parse a parenthesized block */
293 static PARSED_COMMAND *ParseBlock(REDIRECTION *RedirList)
294 {
295 PARSED_COMMAND *Cmd, *Sub, **NextPtr;
296 Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
297 Cmd->Type = C_BLOCK;
298 Cmd->Next = NULL;
299 Cmd->Subcommands = NULL;
300 Cmd->Redirections = RedirList;
301
302 /* Read the block contents */
303 NextPtr = &Cmd->Subcommands;
304 InsideBlock++;
305 while (1)
306 {
307 Sub = ParseCommandOp(C_OP_LOWEST);
308 if (Sub)
309 {
310 *NextPtr = Sub;
311 NextPtr = &Sub->Next;
312 }
313 else if (bParseError)
314 {
315 InsideBlock--;
316 FreeCommand(Cmd);
317 return NULL;
318 }
319
320 if (CurrentTokenType == TOK_END_BLOCK)
321 break;
322
323 /* Skip past the \n */
324 ParseChar();
325 }
326 InsideBlock--;
327
328 /* Process any trailing redirections */
329 while (ParseToken(0, STANDARD_SEPS) == TOK_REDIRECTION)
330 {
331 if (!ParseRedirection(&Cmd->Redirections))
332 {
333 FreeCommand(Cmd);
334 return NULL;
335 }
336 }
337 return Cmd;
338 }
339
340 /* Parse an IF statement */
341 static PARSED_COMMAND *ParseIf(void)
342 {
343 PARSED_COMMAND *Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
344 int Type;
345 memset(Cmd, 0, sizeof(PARSED_COMMAND));
346 Cmd->Type = C_IF;
347
348 Type = CurrentTokenType;
349 if (_tcsicmp(CurrentToken, _T("/I")) == 0)
350 {
351 Cmd->If.Flags |= IFFLAG_IGNORECASE;
352 Type = ParseToken(0, STANDARD_SEPS);
353 }
354 if (_tcsicmp(CurrentToken, _T("not")) == 0)
355 {
356 Cmd->If.Flags |= IFFLAG_NEGATE;
357 Type = ParseToken(0, STANDARD_SEPS);
358 }
359
360 if (Type != TOK_NORMAL)
361 {
362 FreeCommand(Cmd);
363 ParseError();
364 return NULL;
365 }
366
367 /* Check for unary operators */
368 for (; Cmd->If.Operator <= IF_MAX_UNARY; Cmd->If.Operator++)
369 {
370 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
371 {
372 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
373 {
374 FreeCommand(Cmd);
375 ParseError();
376 return NULL;
377 }
378 Cmd->If.RightArg = cmd_dup(CurrentToken);
379 goto condition_done;
380 }
381 }
382
383 /* It must be a two-argument (comparison) operator. It could be ==, so
384 * the equals sign can't be treated as whitespace here. */
385 Cmd->If.LeftArg = cmd_dup(CurrentToken);
386 ParseToken(0, _T(",;"));
387
388 /* The right argument can come immediately after == */
389 if (_tcsnicmp(CurrentToken, _T("=="), 2) == 0 && CurrentToken[2])
390 {
391 Cmd->If.RightArg = cmd_dup(&CurrentToken[2]);
392 goto condition_done;
393 }
394
395 for (; Cmd->If.Operator <= IF_MAX_COMPARISON; Cmd->If.Operator++)
396 {
397 if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
398 {
399 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
400 break;
401 Cmd->If.RightArg = cmd_dup(CurrentToken);
402 goto condition_done;
403 }
404 }
405 FreeCommand(Cmd);
406 ParseError();
407 return NULL;
408
409 condition_done:
410 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
411 if (Cmd->Subcommands == NULL)
412 {
413 FreeCommand(Cmd);
414 return NULL;
415 }
416 if (_tcsicmp(CurrentToken, _T("else")) == 0)
417 {
418 Cmd->Subcommands->Next = ParseCommandOp(C_OP_LOWEST);
419 if (Cmd->Subcommands->Next == NULL)
420 {
421 FreeCommand(Cmd);
422 return NULL;
423 }
424 }
425
426 return Cmd;
427 }
428
429 /*
430 * Parse a FOR command.
431 * Syntax is: FOR [options] %var IN (list) DO command
432 */
433 static PARSED_COMMAND *ParseFor(void)
434 {
435 PARSED_COMMAND *Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
436 TCHAR List[CMDLINE_LENGTH];
437 TCHAR *Pos = List;
438
439 memset(Cmd, 0, sizeof(PARSED_COMMAND));
440 Cmd->Type = C_FOR;
441
442 while (1)
443 {
444 if (_tcsicmp(CurrentToken, _T("/D")) == 0)
445 Cmd->For.Switches |= FOR_DIRS;
446 else if (_tcsicmp(CurrentToken, _T("/F")) == 0)
447 {
448 Cmd->For.Switches |= FOR_F;
449 if (!Cmd->For.Params)
450 {
451 ParseToken(0, STANDARD_SEPS);
452 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
453 break;
454 Cmd->For.Params = cmd_dup(CurrentToken);
455 }
456 }
457 else if (_tcsicmp(CurrentToken, _T("/L")) == 0)
458 Cmd->For.Switches |= FOR_LOOP;
459 else if (_tcsicmp(CurrentToken, _T("/R")) == 0)
460 {
461 Cmd->For.Switches |= FOR_RECURSIVE;
462 if (!Cmd->For.Params)
463 {
464 ParseToken(0, STANDARD_SEPS);
465 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
466 break;
467 StripQuotes(CurrentToken);
468 Cmd->For.Params = cmd_dup(CurrentToken);
469 }
470 }
471 else
472 break;
473 ParseToken(0, STANDARD_SEPS);
474 }
475
476 /* Make sure there aren't two different switches specified
477 * at the same time, unless they're /D and /R */
478 if ((Cmd->For.Switches & (Cmd->For.Switches - 1)) != 0
479 && Cmd->For.Switches != (FOR_DIRS | FOR_RECURSIVE))
480 {
481 goto error;
482 }
483
484 /* Variable name should be % and just one other character */
485 if (CurrentToken[0] != _T('%') || _tcslen(CurrentToken) != 2)
486 goto error;
487 Cmd->For.Variable = CurrentToken[1];
488
489 ParseToken(0, STANDARD_SEPS);
490 if (_tcsicmp(CurrentToken, _T("in")) != 0)
491 goto error;
492
493 if (ParseToken(_T('('), STANDARD_SEPS) != TOK_BEGIN_BLOCK)
494 goto error;
495
496 while (1)
497 {
498 int Type;
499
500 /* Pretend we're inside a block so the tokenizer will stop on ')' */
501 InsideBlock++;
502 Type = ParseToken(0, STANDARD_SEPS);
503 InsideBlock--;
504
505 if (Type == TOK_END_BLOCK)
506 break;
507
508 if (Type == TOK_END)
509 {
510 /* Skip past the \n */
511 ParseChar();
512 continue;
513 }
514
515 if (Type != TOK_NORMAL)
516 goto error;
517
518 if (Pos != List)
519 *Pos++ = _T(' ');
520
521 if (Pos + _tcslen(CurrentToken) >= &List[CMDLINE_LENGTH])
522 goto error;
523 Pos = _stpcpy(Pos, CurrentToken);
524 }
525 *Pos = _T('\0');
526 Cmd->For.List = cmd_dup(List);
527
528 ParseToken(0, STANDARD_SEPS);
529 if (_tcsicmp(CurrentToken, _T("do")) != 0)
530 goto error;
531
532 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
533 if (Cmd->Subcommands == NULL)
534 {
535 FreeCommand(Cmd);
536 return NULL;
537 }
538
539 return Cmd;
540
541 error:
542 FreeCommand(Cmd);
543 ParseError();
544 return NULL;
545 }
546
547 /* Parse a REM command */
548 static PARSED_COMMAND *ParseRem(void)
549 {
550 /* Just ignore the rest of the line */
551 while (CurChar && CurChar != _T('\n'))
552 ParseChar();
553 return NULL;
554 }
555
556 static DECLSPEC_NOINLINE PARSED_COMMAND *ParseCommandPart(REDIRECTION *RedirList)
557 {
558 TCHAR ParsedLine[CMDLINE_LENGTH];
559 PARSED_COMMAND *Cmd;
560 PARSED_COMMAND *(*Func)(void);
561
562 TCHAR *Pos = _stpcpy(ParsedLine, CurrentToken) + 1;
563 DWORD_PTR TailOffset = Pos - ParsedLine;
564
565 /* Check for special forms */
566 if ((Func = ParseFor, _tcsicmp(ParsedLine, _T("for")) == 0) ||
567 (Func = ParseIf, _tcsicmp(ParsedLine, _T("if")) == 0) ||
568 (Func = ParseRem, _tcsicmp(ParsedLine, _T("rem")) == 0))
569 {
570 ParseToken(0, STANDARD_SEPS);
571 /* Do special parsing only if it's not followed by /? */
572 if (_tcscmp(CurrentToken, _T("/?")) != 0)
573 {
574 if (RedirList)
575 {
576 ParseError();
577 FreeRedirection(RedirList);
578 return NULL;
579 }
580 return Func();
581 }
582 Pos = _stpcpy(Pos, _T(" /?"));
583 }
584
585 /* Now get the tail */
586 while (1)
587 {
588 int Type = ParseToken(0, NULL);
589 if (Type == TOK_NORMAL)
590 {
591 if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
592 {
593 ParseError();
594 FreeRedirection(RedirList);
595 return NULL;
596 }
597 Pos = _stpcpy(Pos, CurrentToken);
598 }
599 else if (Type == TOK_REDIRECTION)
600 {
601 if (!ParseRedirection(&RedirList))
602 return NULL;
603 }
604 else
605 {
606 break;
607 }
608 }
609 *Pos++ = _T('\0');
610
611 Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.First[Pos - ParsedLine]));
612 Cmd->Type = C_COMMAND;
613 Cmd->Next = NULL;
614 Cmd->Subcommands = NULL;
615 Cmd->Redirections = RedirList;
616 memcpy(Cmd->Command.First, ParsedLine, (Pos - ParsedLine) * sizeof(TCHAR));
617 Cmd->Command.Rest = Cmd->Command.First + TailOffset;
618 return Cmd;
619 }
620
621 static PARSED_COMMAND *ParsePrimary(void)
622 {
623 REDIRECTION *RedirList = NULL;
624 int Type;
625
626 while (IsSeparator(CurChar))
627 {
628 if (CurChar == _T('\n'))
629 return NULL;
630 ParseChar();
631 }
632
633 if (!CurChar)
634 return NULL;
635
636 if (CurChar == _T(':'))
637 {
638 /* "Ignore" the rest of the line.
639 * (Line continuations will still be parsed, though.) */
640 while (ParseToken(0, NULL) != TOK_END)
641 ;
642 return NULL;
643 }
644
645 if (CurChar == _T('@'))
646 {
647 PARSED_COMMAND *Cmd;
648 ParseChar();
649 Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
650 Cmd->Type = C_QUIET;
651 Cmd->Next = NULL;
652 /* @ acts like a unary operator with low precedence,
653 * so call the top-level parser */
654 Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
655 Cmd->Redirections = NULL;
656 return Cmd;
657 }
658
659 /* Process leading redirections and get the head of the command */
660 while ((Type = ParseToken(_T('('), STANDARD_SEPS)) == TOK_REDIRECTION)
661 {
662 if (!ParseRedirection(&RedirList))
663 return NULL;
664 }
665
666 if (Type == TOK_NORMAL)
667 return ParseCommandPart(RedirList);
668 else if (Type == TOK_BEGIN_BLOCK)
669 return ParseBlock(RedirList);
670 else if (Type == TOK_END_BLOCK && !RedirList)
671 return NULL;
672
673 ParseError();
674 FreeRedirection(RedirList);
675 return NULL;
676 }
677
678 static PARSED_COMMAND *ParseCommandOp(int OpType)
679 {
680 PARSED_COMMAND *Cmd, *Left, *Right;
681
682 if (OpType == C_OP_HIGHEST)
683 Cmd = ParsePrimary();
684 else
685 Cmd = ParseCommandOp(OpType + 1);
686
687 if (Cmd && !_tcscmp(CurrentToken, OpString[OpType - C_OP_LOWEST]))
688 {
689 Left = Cmd;
690 Right = ParseCommandOp(OpType);
691 if (!Right)
692 {
693 if (!bParseError)
694 {
695 /* & is allowed to have an empty RHS */
696 if (OpType == C_MULTI)
697 return Left;
698 ParseError();
699 }
700 FreeCommand(Left);
701 return NULL;
702 }
703
704 Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
705 Cmd->Type = OpType;
706 Cmd->Next = NULL;
707 Cmd->Redirections = NULL;
708 Cmd->Subcommands = Left;
709 Left->Next = Right;
710 Right->Next = NULL;
711 }
712
713 return Cmd;
714 }
715
716 PARSED_COMMAND *
717 ParseCommand(LPTSTR Line)
718 {
719 PARSED_COMMAND *Cmd;
720
721 if (Line)
722 {
723 if (!SubstituteVars(Line, ParseLine, _T('%')))
724 return NULL;
725 bLineContinuations = FALSE;
726 }
727 else
728 {
729 if (!ReadLine(ParseLine, FALSE))
730 return NULL;
731 bLineContinuations = TRUE;
732 }
733 bParseError = FALSE;
734 ParsePos = ParseLine;
735 CurChar = _T(' ');
736
737 Cmd = ParseCommandOp(C_OP_LOWEST);
738 if (Cmd)
739 {
740 if (CurrentTokenType != TOK_END)
741 ParseError();
742 if (bParseError)
743 {
744 FreeCommand(Cmd);
745 Cmd = NULL;
746 }
747 bIgnoreEcho = FALSE;
748 }
749 else
750 {
751 bIgnoreEcho = TRUE;
752 }
753 return Cmd;
754 }
755
756
757 /*
758 * Reconstruct a parse tree into text form; used for echoing
759 * batch file commands and FOR instances.
760 */
761 VOID
762 EchoCommand(PARSED_COMMAND *Cmd)
763 {
764 TCHAR Buf[CMDLINE_LENGTH];
765 PARSED_COMMAND *Sub;
766 REDIRECTION *Redir;
767
768 switch (Cmd->Type)
769 {
770 case C_COMMAND:
771 if (SubstituteForVars(Cmd->Command.First, Buf))
772 ConOutPrintf(_T("%s"), Buf);
773 if (SubstituteForVars(Cmd->Command.Rest, Buf))
774 ConOutPrintf(_T("%s"), Buf);
775 break;
776 case C_QUIET:
777 return;
778 case C_BLOCK:
779 ConOutChar(_T('('));
780 Sub = Cmd->Subcommands;
781 if (Sub && !Sub->Next)
782 {
783 /* Single-command block: display all on one line */
784 EchoCommand(Sub);
785 }
786 else if (Sub)
787 {
788 /* Multi-command block: display parenthesis on separate lines */
789 ConOutChar(_T('\n'));
790 do
791 {
792 EchoCommand(Sub);
793 ConOutChar(_T('\n'));
794 Sub = Sub->Next;
795 } while (Sub);
796 }
797 ConOutChar(_T(')'));
798 break;
799 case C_MULTI:
800 case C_IFFAILURE:
801 case C_IFSUCCESS:
802 case C_PIPE:
803 Sub = Cmd->Subcommands;
804 EchoCommand(Sub);
805 ConOutPrintf(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
806 EchoCommand(Sub->Next);
807 break;
808 case C_IF:
809 ConOutPrintf(_T("if"));
810 if (Cmd->If.Flags & IFFLAG_IGNORECASE)
811 ConOutPrintf(_T(" /I"));
812 if (Cmd->If.Flags & IFFLAG_NEGATE)
813 ConOutPrintf(_T(" not"));
814 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf))
815 ConOutPrintf(_T(" %s"), Buf);
816 ConOutPrintf(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
817 if (SubstituteForVars(Cmd->If.RightArg, Buf))
818 ConOutPrintf(_T(" %s "), Buf);
819 Sub = Cmd->Subcommands;
820 EchoCommand(Sub);
821 if (Sub->Next)
822 {
823 ConOutPrintf(_T(" else "));
824 EchoCommand(Sub->Next);
825 }
826 break;
827 case C_FOR:
828 ConOutPrintf(_T("for"));
829 if (Cmd->For.Switches & FOR_DIRS) ConOutPrintf(_T(" /D"));
830 if (Cmd->For.Switches & FOR_F) ConOutPrintf(_T(" /F"));
831 if (Cmd->For.Switches & FOR_LOOP) ConOutPrintf(_T(" /L"));
832 if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPrintf(_T(" /R"));
833 if (Cmd->For.Params)
834 ConOutPrintf(_T(" %s"), Cmd->For.Params);
835 ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
836 EchoCommand(Cmd->Subcommands);
837 break;
838 }
839
840 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
841 {
842 if (SubstituteForVars(Redir->Filename, Buf))
843 ConOutPrintf(_T(" %c%s%s"), _T('0') + Redir->Number,
844 RedirString[Redir->Mode], Buf);
845 }
846 }
847
848 /*
849 * "Unparse" a command into a text form suitable for passing to CMD /C.
850 * Used for pipes. This is basically the same thing as EchoCommand, but
851 * writing into a string instead of to standard output.
852 */
853 TCHAR *
854 Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd)
855 {
856 TCHAR Buf[CMDLINE_LENGTH];
857 PARSED_COMMAND *Sub;
858 REDIRECTION *Redir;
859
860 /*
861 * Since this function has the annoying requirement that it must avoid
862 * overflowing the supplied buffer, define some helper macros to make
863 * this less painful.
864 */
865 #define CHAR(Char) { \
866 if (Out == OutEnd) return NULL; \
867 *Out++ = Char; }
868 #define STRING(String) { \
869 if (Out + _tcslen(String) > OutEnd) return NULL; \
870 Out = _stpcpy(Out, String); }
871 #define PRINTF(Format, ...) { \
872 UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \
873 if (Len > (UINT)(OutEnd - Out)) return NULL; \
874 Out += Len; }
875 #define RECURSE(Subcommand) { \
876 Out = Unparse(Subcommand, Out, OutEnd); \
877 if (!Out) return NULL; }
878
879 switch (Cmd->Type)
880 {
881 case C_COMMAND:
882 /* This is fragile since there could be special characters, but
883 * Windows doesn't bother escaping them, so for compatibility
884 * we probably shouldn't do it either */
885 if (!SubstituteForVars(Cmd->Command.First, Buf)) return NULL;
886 STRING(Buf)
887 if (!SubstituteForVars(Cmd->Command.Rest, Buf)) return NULL;
888 STRING(Buf)
889 break;
890 case C_QUIET:
891 CHAR(_T('@'))
892 RECURSE(Cmd->Subcommands)
893 break;
894 case C_BLOCK:
895 CHAR(_T('('))
896 for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
897 {
898 RECURSE(Sub)
899 if (Sub->Next)
900 CHAR(_T('&'))
901 }
902 CHAR(_T(')'))
903 break;
904 case C_MULTI:
905 case C_IFFAILURE:
906 case C_IFSUCCESS:
907 case C_PIPE:
908 Sub = Cmd->Subcommands;
909 RECURSE(Sub)
910 PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST])
911 RECURSE(Sub->Next)
912 break;
913 case C_IF:
914 STRING(_T("if"))
915 if (Cmd->If.Flags & IFFLAG_IGNORECASE)
916 STRING(_T(" /I"))
917 if (Cmd->If.Flags & IFFLAG_NEGATE)
918 STRING(_T(" not"))
919 if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf))
920 PRINTF(_T(" %s"), Buf)
921 PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
922 if (!SubstituteForVars(Cmd->If.RightArg, Buf)) return NULL;
923 PRINTF(_T(" %s "), Buf)
924 Sub = Cmd->Subcommands;
925 RECURSE(Sub)
926 if (Sub->Next)
927 {
928 STRING(_T(" else "))
929 RECURSE(Sub->Next)
930 }
931 break;
932 case C_FOR:
933 STRING(_T("for"))
934 if (Cmd->For.Switches & FOR_DIRS) STRING(_T(" /D"))
935 if (Cmd->For.Switches & FOR_F) STRING(_T(" /F"))
936 if (Cmd->For.Switches & FOR_LOOP) STRING(_T(" /L"))
937 if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R"))
938 if (Cmd->For.Params)
939 PRINTF(_T(" %s"), Cmd->For.Params)
940 PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List)
941 RECURSE(Cmd->Subcommands)
942 break;
943 }
944
945 for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
946 {
947 if (!SubstituteForVars(Redir->Filename, Buf)) return NULL;
948 PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number,
949 RedirString[Redir->Mode], Buf)
950 }
951 return Out;
952 }
953
954 VOID
955 FreeCommand(PARSED_COMMAND *Cmd)
956 {
957 if (Cmd->Subcommands)
958 FreeCommand(Cmd->Subcommands);
959 if (Cmd->Next)
960 FreeCommand(Cmd->Next);
961 FreeRedirection(Cmd->Redirections);
962 if (Cmd->Type == C_IF)
963 {
964 cmd_free(Cmd->If.LeftArg);
965 cmd_free(Cmd->If.RightArg);
966 }
967 else if (Cmd->Type == C_FOR)
968 {
969 cmd_free(Cmd->For.Params);
970 cmd_free(Cmd->For.List);
971 }
972 cmd_free(Cmd);
973 }