ReactOS  0.4.13-dev-92-gf251225
parser.c
Go to the documentation of this file.
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 
34 {
35  return _istspace(Char) || (Char && _tcschr(STANDARD_SEPS, Char));
36 }
37 
38 enum
39 {
46 };
47 
51 static TCHAR *ParsePos;
52 static TCHAR CurChar;
53 
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--;
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 {
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 {
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  {
167  *Out++ = Char;
168  ParseChar();
169  }
170  else if (Char == _T(')'))
171  {
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  {
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 
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 */
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;
282  Redir->Number = Number;
283  Redir->Mode = RedirMode;
284  _tcscpy(Redir->Filename, Tok);
285  *List = Redir;
286  return TRUE;
287 
288 fail:
289  ParseError();
291  *List = NULL;
292  return FALSE;
293 }
294 
295 static PARSED_COMMAND *ParseCommandOp(int OpType);
296 
297 /* Parse a parenthesized block */
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  {
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 
334  break;
335 
336  /* Skip past the \n */
337  ParseChar();
338  }
339  InsideBlock--;
340 
341  /* Process any trailing redirections */
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;
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 
370  if (_tcsicmp(CurrentToken, _T("/I")) == 0)
371  {
372  Cmd->If.Flags |= IFFLAG_IGNORECASE;
374  }
375  if (_tcsicmp(CurrentToken, _T("not")) == 0)
376  {
377  Cmd->If.Flags |= IFFLAG_NEGATE;
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  {
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  {
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  */
455 {
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  {
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  {
493  if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
494  break;
496  Cmd->For.Params = cmd_dup(CurrentToken);
497  }
498  }
499  else
500  break;
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 
518  if (_tcsicmp(CurrentToken, _T("in")) != 0)
519  goto error;
520 
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++;
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 
550  goto error;
552  }
553  *Pos = _T('\0');
554  Cmd->For.List = cmd_dup(List);
555 
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 */
577 {
578  /* Just ignore the rest of the line */
579  while (CurChar && CurChar != _T('\n'))
580  ParseChar();
581  return NULL;
582 }
583 
585 {
586  TCHAR ParsedLine[CMDLINE_LENGTH];
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  {
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  }
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 
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  {
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 
767 {
769 
770  if (Line)
771  {
772  if (!SubstituteVars(Line, ParseLine, _T('%')))
773  return NULL;
775  }
776  else
777  {
778  if (!ReadLine(ParseLine, FALSE))
779  return NULL;
781  }
782  bParseError = FALSE;
784  CurChar = _T(' ');
785 
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
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 *
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
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 }
#define FOR_DIRS
Definition: cmd.h:200
static int CurrentTokenType
Definition: parser.c:55
static DECLSPEC_NOINLINE PARSED_COMMAND * ParseCommandPart(REDIRECTION *RedirList)
Definition: parser.c:584
Definition: cmd.h:298
Definition: cmd.h:298
#define TRUE
Definition: types.h:120
struct png_info_def **typedef void(__cdecl typeof(png_destroy_read_struct))(struct png_struct_def **
Definition: typeof.h:49
Type
Definition: Type.h:6
BOOL bIgnoreEcho
Definition: cmd.c:155
#define RECURSE(Subcommand)
Definition: cmd.h:298
#define _tcsicmp
Definition: xmlstorage.h:205
enum _REDIR_MODE REDIR_MODE
#define IFFLAG_IGNORECASE
Definition: cmd.h:227
static TCHAR CurrentToken[CMDLINE_LENGTH]
Definition: parser.c:54
int _tcscmp(const _TCHAR *s1, const _TCHAR *s2)
Definition: tcscmp.h:8
VOID ConOutChar(TCHAR c)
Definition: console.c:123
#define IF_MAX_UNARY
#define WARN(fmt,...)
Definition: debug.h:111
ush Pos
Definition: deflate.h:92
#define CMDLINE_LENGTH
Definition: help.h:12
static PARSED_COMMAND * ParseBlock(REDIRECTION *RedirList)
Definition: parser.c:298
#define INVALID_HANDLE_VALUE
Definition: compat.h:391
_TCHAR * _tcscpy(_TCHAR *to, const _TCHAR *from)
Definition: tcscpy.h:8
Definition: parser.c:40
BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
Definition: cmd.c:1206
Definition: cmd.h:298
#define ConOutPrintf(szStr,...)
Definition: console.h:42
Definition: shell.h:41
static BOOL IsSeparator(TCHAR Char)
Definition: parser.c:33
VOID FreeCommand(PARSED_COMMAND *Cmd)
Definition: parser.c:1015
static VOID StripQuotes(LPSTR in)
Definition: cmdcons.c:116
#define FOR_F
Definition: cmd.h:201
struct _PARSED_COMMAND * Next
Definition: cmd.h:302
static int ParseToken(TCHAR ExtraEnd, TCHAR *Separators)
Definition: parser.c:106
CHAR * LPTSTR
Definition: xmlstorage.h:192
IN PVCB IN PBCB OUT PDIRENT IN USHORT IN POEM_STRING Filename
Definition: fatprocs.h:925
#define C_OP_LOWEST
Definition: parser.c:7
REDIR_MODE Mode
Definition: cmd.h:357
static BOOL bLineContinuations
Definition: parser.c:49
HANDLE OldHandle
Definition: cmd.h:355
static PARSED_COMMAND * ParseFor(void)
Definition: parser.c:454
static BOOL ParseRedirection(REDIRECTION **List)
Definition: parser.c:226
LPTSTR _stpcpy(LPTSTR, LPCTSTR)
Definition: misc.c:461
#define PRINTF(Format,...)
static BOOL bParseError
Definition: parser.c:48
unsigned int BOOL
Definition: ntddk_ex.h:94
#define _tcsnicmp
Definition: xmlstorage.h:207
char Char
Definition: bzip2.c:161
#define FOR_LOOP
Definition: cmd.h:202
size_t __cdecl _tcslen(const _TCHAR *str)
Definition: tcslen.h:9
smooth NULL
Definition: ftsmooth.c:416
static PARSED_COMMAND * ParseIf(void)
Definition: parser.c:354
_TCHAR * _tcschr(const _TCHAR *s, _XINT c)
Definition: tcschr.h:4
PARSED_COMMAND * ParseCommand(LPTSTR Line)
Definition: parser.c:766
void restart(int argc, const char *argv[])
Definition: cmds.c:2115
BYTE Number
Definition: cmd.h:356
char TCHAR
Definition: xmlstorage.h:189
static PARSED_COMMAND * ParseRem(void)
Definition: parser.c:576
#define _T(x)
Definition: vfdio.h:22
Definition: cmd.h:298
LIST_ENTRY List
Definition: psmgr.c:57
static TCHAR ParseLine[CMDLINE_LENGTH]
Definition: parser.c:50
static TCHAR * ParsePos
Definition: parser.c:51
#define IFFLAG_NEGATE
Definition: cmd.h:226
TCHAR Filename[]
Definition: cmd.h:358
DWORD error
Definition: parser.c:105
static const TCHAR *const IfOperatorString[]
Definition: parser.c:13
#define STRING(String)
static TCHAR ParseChar(void)
Definition: parser.c:58
BOOL ReadLine(TCHAR *commandline, BOOL bMore)
Definition: cmd.c:1435
Definition: ncftp.h:79
#define FOR_RECURSIVE
Definition: cmd.h:203
static const TCHAR RedirString[][3]
Definition: parser.c:11
Definition: sacdrv.h:277
#define memcpy(s1, s2, n)
Definition: mkisofs.h:878
unsigned char BYTE
Definition: mem.h:68
static PARSED_COMMAND * ParseCommandOp(int OpType)
Definition: parser.c:719
uint32_t DWORD_PTR
Definition: typedefs.h:63
TCHAR * Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd)
Definition: parser.c:905
_In_opt_ PENTER_STATE_SYSTEM_HANDLER _In_opt_ PVOID _In_ LONG _In_opt_ LONG volatile * Number
Definition: ntpoapi.h:204
Definition: cmd.h:298
BOOL SubstituteForVars(TCHAR *Src, TCHAR *Dest)
Definition: cmd.c:1372
#define STANDARD_SEPS
Definition: parser.c:31
VOID error_syntax(LPTSTR)
Definition: error.c:150
#define cmd_alloc(size)
Definition: cmddbg.h:29
static TCHAR CurChar
Definition: parser.c:52
#define cmd_free(ptr)
Definition: cmddbg.h:31
void(* Func)(int)
static void ParseError(void)
Definition: parser.c:96
#define cmd_dup(str)
Definition: cmddbg.h:32
#define FIELD_OFFSET(t, f)
Definition: typedefs.h:254
static int InsideBlock
Definition: parser.c:56
static const TCHAR OpString[][3]
Definition: parser.c:9
VOID FreeRedirection(REDIRECTION *)
Definition: redir.c:153
struct _REDIRECTION * Next
Definition: cmd.h:354
Definition: cmd.h:298
#define _istspace
Definition: tchar.h:1504
#define IF_MAX_COMPARISON
#define C_OP_HIGHEST
Definition: parser.c:8
#define memset(x, y, z)
Definition: compat.h:39
#define CHAR(Char)
#define DECLSPEC_NOINLINE
Definition: ntbasedef.h:231
VOID EchoCommand(PARSED_COMMAND *Cmd)
Definition: parser.c:811
static PARSED_COMMAND * ParsePrimary(void)
Definition: parser.c:656