ReactOS  0.4.15-dev-2991-g632fa1c
parser.c
Go to the documentation of this file.
1 /*
2  * PARSER.C - Command-line Lexical Analyzer/Tokenizer and Parser.
3  */
4 
5 #include "precomp.h"
6 
7 /*
8  * Defines for enabling different Windows' CMD compatibility behaviours.
9  */
10 
11 /* Enable this define for command echoer compatibility */
12 #define MSCMD_ECHO_COMMAND_COMPAT
13 
14 /* Enable this define for parser quirks (see UnParseToken() for more details) */
15 #define MSCMD_PARSER_BUGS
16 
17 /* Enable this define for parenthesized blocks parsing quirks */
18 // #define MSCMD_PARENS_PARSE_BUGS
19 
20 /* Enable this define for redirection parsing quirks */
21 #define MSCMD_REDIR_PARSE_BUGS
22 
23 /* Enable this define for allowing '&' commands with an empty RHS.
24  * The default behaviour is to just return the LHS instead.
25  * See ParseCommandBinaryOp() for details. */
26 // #define MSCMD_MULTI_EMPTY_RHS
27 
28 
29 /*
30  * Parser debugging support. These flags are global so that their values can be
31  * modified at runtime from a debugger. They correspond to the public Windows'
32  * cmd!fDumpTokens and cmd!fDumpParse booleans.
33  * (Same names are used for compatibility as they are documented online.)
34  */
37 
38 #define C_OP_LOWEST C_MULTI
39 #define C_OP_HIGHEST C_PIPE
40 static const TCHAR OpString[][3] = { _T("&"), _T("||"), _T("&&"), _T("|") };
41 
42 static const TCHAR RedirString[][3] = { _T("<"), _T(">"), _T(">>") };
43 
44 static const TCHAR* const IfOperatorString[] =
45 {
48  /* Standard */
49  _T("errorlevel"),
50  _T("exist"),
51 
52  /* Extended */
53  _T("cmdextversion"),
54  _T("defined"),
55 #define IF_MAX_UNARY IF_DEFINED
56 
59  /* Standard */
60  _T("=="),
61 
62  /* Extended */
63  _T("equ"),
64  _T("neq"),
65  _T("lss"),
66  _T("leq"),
67  _T("gtr"),
68  _T("geq"),
69 #define IF_MAX_COMPARISON IF_GEQ
70 };
71 
72 static __inline BOOL IsSeparator(TCHAR Char)
73 {
74  return _istspace(Char) || (Char && !!_tcschr(STANDARD_SEPS, Char));
75 }
76 
77 typedef enum _TOK_TYPE
78 {
85 } TOK_TYPE;
86 
87 /* Scratch buffer for temporary command substitutions / expansions */
89 
90 /*static*/ BOOL bParseError;
95 
98 
101 #ifndef MSCMD_PARSER_BUGS
102 static BOOL bReparseToken = FALSE;
103 static PTCHAR LastCurTokPos;
104 #endif
105 static INT InsideBlock = 0;
106 
108 {
109  bParseError = FALSE;
110  ParsePos = Pos;
112 }
113 
114 /*
115  * This function "refetches" the last parsed token back into the stream
116  * for later reparsing -- since the way of lexing it is context-dependent.
117  * This "feature" is at the root of many obscure CMD parsing quirks,
118  * due to the fact this feature is in opposition with line-continuation.
119  * Indeed, when a stream of characters has a line-continuation, the lexer-
120  * parser will parse the stream up to the end of the line, then will
121  * reset the parser state and position back to the beginning of the line
122  * before accepting the rest of the character stream and continuing
123  * parsing them. This means that all the non-parsed characters before the
124  * line-continuation have been lost. Of course, their parsed form is now
125  * within the current parsed token. However, suppose now we need to
126  * unparse this token for reparsing it a different way later on. If we
127  * somehow pushed the already-parsed current token back into the beginning
128  * of the character stream, besides the complications of moving up the
129  * characters in the stream buffer, we would basically have "new" data
130  * that has been already parsed one way, to be now parsed another way.
131  * If instead we had saved somehow the unparsed form of the token, and
132  * we push back that form into the stream buffer for reparsing, we would
133  * encounter again the line-continuation, that, depending on which
134  * context the token is reparsed, would cause problems:
135  * e.g. in the case of REM command parsing, the parser would stop at the
136  * first line-continuation.
137  *
138  * When MSCMD_PARSER_BUGS is undefined, the UnParseToken() / ParseToken()
139  * cycle keeps the current token in its buffer, but also saves the start
140  * position corresponding to the batch of characters that have been parsed
141  * during the last line-continuation. The next ParseToken() would then
142  * reparse these latest charcters and the result replaces the last part
143  * in the current token.
144  *
145  * For example, a first parsing of
146  * foo^\n
147  * bar^\n
148  * baz
149  * would result in the current token "foobarbaz", where the start position
150  * corresponding to the batch of characters parsed during the last line-continuation
151  * being pointing at "baz". The stream buffer only contains "baz" (and following data).
152  * Then UnParseToken() saves this info so that at the next ParseToken(), the "baz"
153  * part of the stream buffer gets reparsed (possibly differently) and the result
154  * would replace the "baz" part in the current token.
155  *
156  * If MSCMD_PARSER_BUGS is defined however, then the behaviour of the Windows' CMD
157  * parser applies: in the example above, the last ParseToken() call would completely
158  * replace the current token "foobarbaz" with the new result of the parsing of "baz".
159  */
161 {
163 
164  /* Debugging support */
165  if (fDumpTokens)
166  ConOutPrintf(_T("Ungetting: '%s'\n"), ParsePos);
167 
168 #ifndef MSCMD_PARSER_BUGS
169  bReparseToken = TRUE;
170 #endif
171 }
172 
174 {
175  *CurrentToken = 0;
177  InsideBlock = 0;
178 
179 #ifndef MSCMD_PARSER_BUGS
180  bReparseToken = FALSE;
181  LastCurTokPos = NULL;
182 #endif
183 
185 }
186 
188 {
189  TCHAR Char;
190 
191  if (bParseError)
192  return 0;
193 
194 restart:
195  /*
196  * Although CRs can be injected into a line via an environment
197  * variable substitution, the parser ignores them - they won't
198  * even separate tokens.
199  */
200  do
201  {
202  Char = *ParsePos++;
203  }
204  while (Char == _T('\r'));
205 
206  if (!Char) --ParsePos;
207  if (!Char && bLineContinuations)
208  {
209  if (!ReadLine(ParseLine, TRUE))
210  {
211  /* ^C pressed, or line was too long */
212  //
213  // FIXME: Distinguish with respect to BATCH end of file !!
214  //
215  bParseError = TRUE;
216  }
217  else
218  {
220  if (*ParsePos)
221  goto restart;
222  }
223  }
224  return Char;
225 }
226 
228 {
229  /* Only display the first error we encounter */
230  if (!bParseError)
231  error_syntax(s);
232  bParseError = TRUE;
233 }
234 
235 static __inline VOID ParseError(VOID)
236 {
238 }
239 
240 static TOK_TYPE
242  IN TCHAR PrefixOperator OPTIONAL,
243  IN TCHAR ExtraEnd OPTIONAL,
244  IN PCTSTR Separators OPTIONAL,
246 {
247  TOK_TYPE Type;
248  PTCHAR CurrentTokStart = CurrentToken;
249  PTCHAR Out = CurrentTokStart;
250  TCHAR Char;
251  BOOL bInQuote = FALSE;
252 
253 #ifndef MSCMD_PARSER_BUGS
254  if (bReparseToken)
255  {
256  bReparseToken = FALSE;
257 
258  /*
259  * We will append the part to be reparsed to the old one
260  * (still present in CurrentToken).
261  */
262  CurrentTokStart = LastCurTokPos;
263  Out = CurrentTokStart;
264  }
265  else
266  {
267  LastCurTokPos = CurrentToken;
268  }
269 #endif
270 
271  /* Start with what we have at current ParsePos */
273 
274  for (Char = ParseChar(); Char && Char != _T('\n'); Char = ParseChar())
275  {
276  bInQuote ^= (Char == _T('"'));
277  if (!bInQuote)
278  {
279  if (Separators != NULL)
280  {
281  if (_istspace(Char) || !!_tcschr(Separators, Char))
282  {
283  /* Skip leading separators */
284  if (Out == CurrentTokStart)
285  continue;
286  break;
287  }
288  }
289 
290  /* Check for prefix operator */
291  if ((Out == CurrentTokStart) && (Char == PrefixOperator))
292  break;
293 
294  /*
295  * Check for numbered redirection.
296  *
297  * For this purpose, we check whether this is a number, that is
298  * in first position in the current parsing buffer (remember that
299  * ParsePos points to the next character) or is preceded by a
300  * whitespace-like separator, including standard command operators
301  * (excepting '@' !) and double-quotes.
302  */
303  if ( _istdigit(Char) &&
304  (ParsePos == &OldParsePos[1] ||
305  IsSeparator(ParsePos[-2]) ||
306  !!_tcschr(_T("()&|\""), ParsePos[-2])) &&
307  (*ParsePos == _T('<') || *ParsePos == _T('>')) )
308  {
309  break;
310  }
311 
312  /* Check for other delimiters / operators */
313  if (Char == ExtraEnd)
314  break;
315  if (InsideBlock && Char == _T(')'))
316  break;
317  if (_tcschr(_T("&|<>"), Char))
318  break;
319 
320  if (bHandleContinuations && (Char == _T('^')))
321  {
322  Char = ParseChar();
323  /* Eat up a \n, allowing line continuation */
324  if (Char == _T('\n'))
325  {
326 #ifndef MSCMD_PARSER_BUGS
327  LastCurTokPos = Out;
328 #endif
329  Char = ParseChar();
330  }
331  /* Next character is a forced literal */
332 
333  if (Out == CurrentTokStart)
334  {
335  /* Ignore any prefix operator if we don't start a new command block */
337  PrefixOperator = 0;
338  }
339  }
340  }
341  if (Out == &CurrentToken[CMDLINE_LENGTH - 1])
342  break;
343  *Out++ = Char;
344 
345  // PrefixOperator = 0;
346  }
347 
348  /*
349  * We exited the parsing loop. If the current character is the first one
350  * (Out == CurrentTokStart), interpret it as an operator. Otherwise,
351  * terminate the current token (type TOK_NORMAL) and keep the current
352  * character so that it can be refetched as an operator at the next call.
353  */
354 
355  if (Out != CurrentTokStart)
356  {
357  Type = TOK_NORMAL;
358  }
359  /*
360  * Else we have an operator.
361  */
362  else if (Char == _T('@'))
363  {
364  Type = TOK_OPERATOR; // TOK_QUIET / TOK_PREFIX_OPERATOR
365  *Out++ = Char;
366  Char = ParseChar();
367  }
368  else if (Char == _T('('))
369  {
371  *Out++ = Char;
372  Char = ParseChar();
373  }
374  else if (Char == _T(')'))
375  {
377  *Out++ = Char;
378  Char = ParseChar();
379  }
380  else if (Char == _T('&') || Char == _T('|'))
381  {
382  Type = TOK_OPERATOR;
383  *Out++ = Char;
384  Char = ParseChar();
385  /* Check for '&&' or '||' */
386  if (Char == Out[-1])
387  {
388  *Out++ = Char;
389  Char = ParseChar();
390  }
391  }
392  else if ( _istdigit(Char) ||
393  (Char == _T('<') || Char == _T('>')) )
394  {
396  if (_istdigit(Char))
397  {
398  *Out++ = Char;
399  Char = ParseChar();
400  }
401  /* By construction (see the while-loop above),
402  * the next character must be a redirection. */
403  ASSERT(Char == _T('<') || Char == _T('>'));
404  *Out++ = Char;
405  Char = ParseChar();
406  if (Char == Out[-1])
407  {
408  /* Strangely, the tokenizer allows << as well as >>... (it
409  * will cause an error when trying to parse it though) */
410  *Out++ = Char;
411  Char = ParseChar();
412  }
413  if (Char == _T('&'))
414  {
415  *Out++ = Char;
416  while (IsSeparator(Char = ParseChar()))
417  ;
418  if (_istdigit(Char))
419  {
420  *Out++ = Char;
421  Char = ParseChar();
422  }
423  }
424  }
425  else
426  {
427  Type = TOK_END;
428  *Out++ = Char;
429  }
430  *Out = _T('\0');
431 
432  /*
433  * Rewind the parsing position, so that the current character can be
434  * refetched later on. However do this only if it is not NULL and if
435  * this is not TOK_END, since we do not want to reparse later the line
436  * termination (we could enter into infinite loops, or, in case of line
437  * continuation, get unwanted "More?" prompts).
438  */
439  if (Char != 0 && Type != TOK_END)
440  --ParsePos;
441 
442  /* Debugging support */
443  if (fDumpTokens)
444  ConOutPrintf(_T("ParseToken: (%d) '%s'\n"), Type, CurrentToken);
445 
446  return (CurrentTokenType = Type);
447 }
448 
449 static __inline INT
451  IN TCHAR ExtraEnd OPTIONAL,
452  IN PCTSTR Separators OPTIONAL)
453 {
454  return ParseTokenEx(0, ExtraEnd, Separators, bHandleContinuations);
455 }
456 
457 
458 static PARSED_COMMAND*
461  IN PCTSTR CmdHead OPTIONAL,
462  IN PCTSTR CmdTail OPTIONAL)
463 {
465 
466  switch (Type)
467  {
468  case C_COMMAND:
469  case C_REM:
470  {
471  SIZE_T CmdHeadLen = _tcslen(CmdHead) + 1;
472  SIZE_T CmdTailLen = _tcslen(CmdTail) + 1;
473 
475  Command.First[CmdHeadLen + CmdTailLen]));
476  if (!Cmd)
477  return NULL;
478 
479  Cmd->Type = Type;
480  Cmd->Next = NULL;
481  Cmd->Subcommands = NULL;
482  Cmd->Redirections = NULL; /* Is assigned by the calling function */
483  memcpy(Cmd->Command.First, CmdHead, CmdHeadLen * sizeof(TCHAR));
484  Cmd->Command.Rest = Cmd->Command.First + CmdHeadLen;
485  memcpy(Cmd->Command.Rest, CmdTail, CmdTailLen * sizeof(TCHAR));
486  return Cmd;
487  }
488 
489  case C_QUIET:
490  case C_BLOCK:
491  case C_MULTI:
492  case C_OR:
493  case C_AND:
494  case C_PIPE:
495  {
496  Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
497  if (!Cmd)
498  return NULL;
499 
500  Cmd->Type = Type;
501  Cmd->Next = NULL;
502  Cmd->Subcommands = NULL;
503  Cmd->Redirections = NULL; /* For C_BLOCK only: is assigned by the calling function */
504  return Cmd;
505  }
506 
507  case C_FOR:
508  case C_IF:
509  {
510  Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
511  if (!Cmd)
512  return NULL;
513 
514  memset(Cmd, 0, sizeof(PARSED_COMMAND));
515  Cmd->Type = Type;
516  return Cmd;
517  }
518 
519  default:
520  ERR("Unknown command type 0x%x\n", Type);
521  ASSERT(FALSE);
522  return NULL;
523  }
524 }
525 
526 VOID
529 {
530  if (Cmd->Subcommands)
531  FreeCommand(Cmd->Subcommands);
532  if (Cmd->Next)
533  FreeCommand(Cmd->Next);
534  FreeRedirection(Cmd->Redirections);
535  if (Cmd->Type == C_FOR)
536  {
537  cmd_free(Cmd->For.Params);
538  cmd_free(Cmd->For.List);
539  }
540  else if (Cmd->Type == C_IF)
541  {
542  cmd_free(Cmd->If.LeftArg);
543  cmd_free(Cmd->If.RightArg);
544  }
545  cmd_free(Cmd);
546 }
547 
548 
549 /* Parse redirections and append them to the list */
550 static BOOL
553 {
554  PTSTR Tok = CurrentToken;
555  REDIRECTION* Redir;
556  REDIR_MODE RedirMode;
557  BYTE Number;
558 
559  if ( !(*Tok == _T('<') || *Tok == _T('>')) &&
560  !(_istdigit(*Tok) &&
561  (Tok[1] == _T('<') || Tok[1] == _T('>')) ) )
562  {
564  return FALSE;
565  }
568 
569  if (_istdigit(*Tok))
570  Number = *Tok++ - _T('0');
571  else
572  Number = *Tok == _T('<') ? 0 : 1;
573 
574  if (*Tok++ == _T('<'))
575  {
576  RedirMode = REDIR_READ;
577  /* Forbid '<<' */
578  if (*Tok == _T('<'))
579  goto fail;
580  }
581  else
582  {
583  RedirMode = REDIR_WRITE;
584  if (*Tok == _T('>'))
585  {
586  RedirMode = REDIR_APPEND;
587  Tok++;
588  }
589  }
590 
591  if (*Tok == _T('&'))
592  {
593  /* This is a handle redirection: the next character must be one single digit */
594  if (!(_istdigit(Tok[1]) && !Tok[2]))
595  goto fail;
596  }
597  else
598 #ifndef MSCMD_REDIR_PARSE_BUGS
599  if (!*Tok)
600  /* The file name was not part of this token, so it will be the next one */
601 #else
602  /* Get rid of what possibly remains in the token, and retrieve the next one */
603 #endif
604  {
606  goto fail;
607  Tok = CurrentToken;
608  }
609 
610  /* If a redirection for this handle number already exists, delete it */
611  while ((Redir = *List))
612  {
613  if (Redir->Number == Number)
614  {
615  *List = Redir->Next;
616  cmd_free(Redir);
617  continue;
618  }
619  List = &Redir->Next;
620  }
621 
622  Redir = cmd_alloc(FIELD_OFFSET(REDIRECTION, Filename[_tcslen(Tok) + 1]));
623  if (!Redir)
624  {
625  WARN("Cannot allocate memory for Redir!\n");
626  goto fail;
627  }
628  Redir->Next = NULL;
630  Redir->Number = Number;
631  Redir->Mode = RedirMode;
632  _tcscpy(Redir->Filename, Tok);
633  *List = Redir;
634  return TRUE;
635 
636 fail:
637  ParseError();
639  *List = NULL;
640  return FALSE;
641 }
642 
643 static __inline PARSED_COMMAND*
645  IN COMMAND_TYPE OpType);
646 
647 /* Parse a parenthesized block */
648 static PARSED_COMMAND*
650  IN OUT REDIRECTION** RedirList)
651 {
652  PARSED_COMMAND *Cmd, *Sub, **NextPtr;
653 
655  if (!Cmd)
656  {
657  WARN("Cannot allocate memory for Cmd!\n");
658  ParseError();
659  return NULL;
660  }
661 
662  /* Read the block contents */
663  NextPtr = &Cmd->Subcommands;
664  ++InsideBlock;
665  while (TRUE)
666  {
667  /*
668  * Windows' CMD compatibility: Strip leading newlines in the block.
669  *
670  * Note that this behaviour is buggy, especially when MSCMD_PARSER_BUGS is defined!
671  * For example:
672  * (foo^\n
673  * bar)
674  * would be parsed ultimately as: '(', 'bar', ')' because the "foo^"
675  * part would be discarded due to the UnParseToken() call, since this
676  * function doesn't work across line continuations.
677  */
678  while (ParseToken(0, STANDARD_SEPS) == TOK_END && *CurrentToken == _T('\n'))
679  ;
680  if (*CurrentToken && *CurrentToken != _T('\n'))
681  UnParseToken();
682 
683  /* Break early if we have nothing else to read. We will also fail
684  * due to the fact we haven't encountered any closing parenthesis. */
685  if (!*CurrentToken /* || *CurrentToken == _T('\n') */)
686  {
688  break;
689  }
690 
691  /*
692  * NOTE: Windows' CMD uses a "CRLF" operator when dealing with
693  * newlines in parenthesized blocks, as an alternative to the
694  * '&' command-separation operator.
695  */
696 
698  if (Sub)
699  {
700  *NextPtr = Sub;
701  NextPtr = &Sub->Next;
702  }
703  else if (bParseError)
704  {
705  --InsideBlock;
706  FreeCommand(Cmd);
707  return NULL;
708  }
709 
711  break;
712 
713  /* Skip past the \n */
714  }
715  --InsideBlock;
716 
717  /* Fail if the block was not terminated, or if we have
718  * an empty block, i.e. "( )", considered invalid. */
719  if ((CurrentTokenType != TOK_END_BLOCK) || (Cmd->Subcommands == NULL))
720  {
721  ParseError();
722  FreeCommand(Cmd);
723  return NULL;
724  }
725 
726  /* Process any trailing redirections and append them to the list */
727 #ifndef MSCMD_REDIR_PARSE_BUGS
729  {
730  if (!ParseRedirection(RedirList))
731  {
732  FreeCommand(Cmd);
733  return NULL;
734  }
735  }
736 #else
737  while (ParseToken(0, STANDARD_SEPS) != TOK_END)
738  {
739  if (!ParseRedirection(RedirList))
740  {
741  /* If an actual error happened in ParseRedirection(), bail out */
742  if (bParseError)
743  {
744  FreeCommand(Cmd);
745  return NULL;
746  }
747  /* Otherwise it just returned FALSE because the current token
748  * is not a redirection. Unparse the token and refetch it. */
749  break;
750  }
751  }
752 #endif
753  if (CurrentTokenType != TOK_END)
754  {
755  /*
756  * Windows' CMD compatibility: Unparse the current token.
757  *
758  * Note that this behaviour is buggy, especially when MSCMD_PARSER_BUGS is defined!
759  * For example:
760  * (foo^\n
761  * bar)
762  * would be parsed ultimately as: '(', 'bar', ')' because the "foo^"
763  * part would be discarded due to the UnParseToken() call, since this
764  * function doesn't work across line continuations.
765  */
766  UnParseToken();
767 
768  /*
769  * Since it is expected that when ParseBlock() returns, the next
770  * token is already fetched, call ParseToken() again to compensate.
771  */
773  }
774 
775  return Cmd;
776 }
777 
778 /* Parse an IF statement */
779 static PARSED_COMMAND*
781 {
783 
785  if (!Cmd)
786  {
787  WARN("Cannot allocate memory for Cmd!\n");
788  ParseError();
789  return NULL;
790  }
791 
792  if (bEnableExtensions && (_tcsicmp(CurrentToken, _T("/I")) == 0))
793  {
794  Cmd->If.Flags |= IFFLAG_IGNORECASE;
796  }
797  if (_tcsicmp(CurrentToken, _T("not")) == 0)
798  {
799  Cmd->If.Flags |= IFFLAG_NEGATE;
801  }
802 
804  goto error;
805 
806  /* Check for unary operators */
807  for (; Cmd->If.Operator <= IF_MAX_UNARY; Cmd->If.Operator++)
808  {
809  /* Skip the extended operators if the extensions are disabled */
810  if (!bEnableExtensions && (Cmd->If.Operator >= IF_CMDEXTVERSION))
811  continue;
812 
813  if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
814  {
816  goto error;
817  Cmd->If.RightArg = cmd_dup(CurrentToken);
818  goto condition_done;
819  }
820  }
821 
822  /* It must be a two-argument (comparison) operator. It could be ==, so
823  * the equals sign can't be treated as whitespace here. */
824  Cmd->If.LeftArg = cmd_dup(CurrentToken);
825  ParseToken(0, _T(",;"));
826 
827  /* The right argument can come immediately after == */
828  if (_tcsnicmp(CurrentToken, _T("=="), 2) == 0 && CurrentToken[2])
829  {
830  Cmd->If.RightArg = cmd_dup(&CurrentToken[2]);
831  goto condition_done;
832  }
833 
834  // Cmd->If.Operator == IF_MAX_UNARY + 1;
835  for (; Cmd->If.Operator <= IF_MAX_COMPARISON; Cmd->If.Operator++)
836  {
837  /* Skip the extended operators if the extensions are disabled */
838  if (!bEnableExtensions && (Cmd->If.Operator >= IF_EQU)) // (Cmd->If.Operator > IF_STRINGEQ)
839  continue;
840 
841  if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
842  {
844  goto error;
845  Cmd->If.RightArg = cmd_dup(CurrentToken);
846  goto condition_done;
847  }
848  }
849  goto error;
850 
851 condition_done:
852  Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
853  if (Cmd->Subcommands == NULL)
854  goto error;
855  if (_tcsicmp(CurrentToken, _T("else")) == 0)
856  {
857  Cmd->Subcommands->Next = ParseCommandOp(C_OP_LOWEST);
858  if (Cmd->Subcommands->Next == NULL)
859  goto error;
860  }
861 
862  return Cmd;
863 
864 error:
865  FreeCommand(Cmd);
866  ParseError();
867  return NULL;
868 }
869 
870 /*
871  * Parse a FOR command.
872  * Syntax is: FOR [options] %var IN (list) DO command
873  */
874 static PARSED_COMMAND*
876 {
878 
879  /* Use the scratch buffer */
880  PTSTR List = TempBuf;
881  PTCHAR Pos = List;
882 
884  if (!Cmd)
885  {
886  WARN("Cannot allocate memory for Cmd!\n");
887  ParseError();
888  return NULL;
889  }
890 
891  /* Skip the extended FOR syntax if extensions are disabled */
892  if (!bEnableExtensions)
893  goto parseForBody;
894 
895  while (TRUE)
896  {
897  if (_tcsicmp(CurrentToken, _T("/D")) == 0)
898  {
899  Cmd->For.Switches |= FOR_DIRS;
900  }
901  else if (_tcsicmp(CurrentToken, _T("/F")) == 0)
902  {
903  Cmd->For.Switches |= FOR_F;
904  if (!Cmd->For.Params)
905  {
907  if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
908  break;
909  Cmd->For.Params = cmd_dup(CurrentToken);
910  }
911  }
912  else if (_tcsicmp(CurrentToken, _T("/L")) == 0)
913  {
914  Cmd->For.Switches |= FOR_LOOP;
915  }
916  else if (_tcsicmp(CurrentToken, _T("/R")) == 0)
917  {
918  Cmd->For.Switches |= FOR_RECURSIVE;
919  if (!Cmd->For.Params)
920  {
922  if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
923  break;
925  Cmd->For.Params = cmd_dup(CurrentToken);
926  }
927  }
928  else
929  {
930  break;
931  }
932 
934  }
935 
936  /* Make sure there aren't two different switches specified
937  * at the same time, unless they're /D and /R */
938  if ((Cmd->For.Switches & (Cmd->For.Switches - 1)) != 0
939  && Cmd->For.Switches != (FOR_DIRS | FOR_RECURSIVE))
940  {
941  goto error;
942  }
943 
944 parseForBody:
945 
946  /* Variable name should be % and just one other character */
947  if (CurrentToken[0] != _T('%') || _tcslen(CurrentToken) != 2)
948  goto error;
949  Cmd->For.Variable = CurrentToken[1];
950 
952  if (_tcsicmp(CurrentToken, _T("in")) != 0)
953  goto error;
954 
956  goto error;
957 
958  while (TRUE)
959  {
960  /* Pretend we're inside a block so the tokenizer will stop on ')' */
961  ++InsideBlock;
963  --InsideBlock;
964 
966  break;
967 
968  /* Skip past the \n */
969  if ((CurrentTokenType == TOK_END) && *CurrentToken == _T('\n'))
970  continue;
971 
973  goto error;
974 
975  if (Pos != List)
976  *Pos++ = _T(' ');
977 
979  goto error;
981  }
982  *Pos = _T('\0');
983  Cmd->For.List = cmd_dup(List);
984 
986  if (_tcsicmp(CurrentToken, _T("do")) != 0)
987  goto error;
988 
989  Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
990  if (Cmd->Subcommands == NULL)
991  goto error;
992 
993  return Cmd;
994 
995 error:
996  FreeCommand(Cmd);
997  ParseError();
998  return NULL;
999 }
1000 
1001 /* Parse a REM command */
1002 static PARSED_COMMAND*
1004 {
1006 
1007  /* The common scratch buffer already contains the name of the command */
1008  PTSTR ParsedLine = TempBuf;
1009 
1010  PTCHAR Pos = ParsedLine + _tcslen(ParsedLine) + 1;
1011  SIZE_T TailOffset = Pos - ParsedLine;
1012 
1013  /* Build a minimal command for REM, so that it can still get through the batch echo unparsing */
1014 
1015  /* Unparse the current token, so as to emulate the REM command parsing
1016  * behaviour of Windows' CMD, that discards everything before the last
1017  * line continuation. */
1018  UnParseToken();
1019 
1020  /*
1021  * Ignore the rest of the line, without any line continuation (but eat the caret).
1022  * We cannot simply set bLineContinuations to TRUE or FALSE, because we want (only
1023  * for the REM command), even when bLineContinuations == FALSE, to get the caret,
1024  * otherwise it would be ignored.
1025  */
1026  while (ParseTokenEx(0, 0, NULL, FALSE) != TOK_END)
1027  {
1028  if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
1029  {
1030  ParseError();
1031  return NULL;
1032  }
1034  }
1035  *Pos = _T('\0');
1036 
1038  ParsedLine,
1039  ParsedLine + TailOffset);
1040  if (!Cmd)
1041  {
1042  WARN("Cannot allocate memory for Cmd!\n");
1043  ParseError();
1044  return NULL;
1045  }
1046  return Cmd;
1047 }
1048 
1049 /* Parse a command */
1050 static PARSED_COMMAND*
1052  IN OUT REDIRECTION** RedirList)
1053 {
1055  PARSED_COMMAND* (*Func)(VOID);
1056 
1057  /* Use the scratch buffer */
1058  PTSTR ParsedLine = TempBuf;
1059 
1060  /* We need to copy the current token because it's going to be changed below by the ParseToken() calls */
1061  PTCHAR Pos = _stpcpy(ParsedLine, CurrentToken) + 1;
1062  SIZE_T TailOffset = Pos - ParsedLine;
1063 
1064  /* Check for special forms */
1065  if ((Func = ParseFor, _tcsicmp(ParsedLine, _T("FOR")) == 0) ||
1066  (Func = ParseIf, _tcsicmp(ParsedLine, _T("IF")) == 0) ||
1067  (Func = ParseRem, _tcsicmp(ParsedLine, _T("REM")) == 0))
1068  {
1069  PTCHAR pHelp;
1070 
1072 
1073  if ((pHelp = _tcsstr(CurrentToken, _T("/?"))) &&
1074  (Func == ParseIf ? (pHelp[2] == _T('/') || pHelp[2] == 0) : TRUE))
1075  {
1076  /* /? was found within the first token */
1078  }
1079  else
1080  {
1081  pHelp = NULL;
1082  }
1083  if (pHelp && (CurrentTokenType == TOK_NORMAL))
1084  {
1085  /* We encountered /? first, but is followed
1086  * by another token: that's an error. */
1087  ParseError();
1088  return NULL;
1089  }
1090 
1091  /* Do actual parsing only if no help is present */
1092  if (!pHelp)
1093  {
1094  /* FOR and IF commands cannot have leading redirection, but REM can */
1095  if (*RedirList && ((Func == ParseFor) || (Func == ParseIf)))
1096  {
1097  /* Display the culprit command and fail */
1098  ParseErrorEx(ParsedLine);
1099  return NULL;
1100  }
1101 
1102  return Func();
1103  }
1104 
1105  /* Otherwise, run FOR,IF,REM as regular commands only for help support */
1106  if (Pos + _tcslen(_T("/?")) >= &ParsedLine[CMDLINE_LENGTH])
1107  {
1108  ParseError();
1109  return NULL;
1110  }
1111  Pos = _stpcpy(Pos, _T("/?"));
1112  }
1113  else
1114  {
1115  ParseToken(0, NULL);
1116  }
1117 
1118  /* Now get the tail */
1119  while (CurrentTokenType != TOK_END)
1120  {
1122  {
1123  if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
1124  {
1125  ParseError();
1126  return NULL;
1127  }
1129  }
1130 #ifndef MSCMD_REDIR_PARSE_BUGS
1131  else if (CurrentTokenType == TOK_REDIRECTION)
1132  {
1133  /* Process any trailing redirections and append them to the list */
1135  {
1136  if (!ParseRedirection(RedirList))
1137  return NULL;
1138 
1140  }
1141  if (CurrentTokenType == TOK_END)
1142  break;
1143 
1144  /* Unparse the current token, and reparse it below with no separators */
1145  UnParseToken();
1146  }
1147  else
1148  {
1149  /* There is no need to do a UnParseToken() / ParseToken() cycle */
1150  break;
1151  }
1152 #else
1153  else
1154  {
1155  /* Process any trailing redirections and append them to the list */
1156  BOOL bSuccess = FALSE;
1157 
1159 
1160  while (CurrentTokenType != TOK_END)
1161  {
1162  if (!ParseRedirection(RedirList))
1163  {
1164  /* If an actual error happened in ParseRedirection(), bail out */
1165  if (bParseError)
1166  return NULL;
1167 
1168  /* Otherwise it just returned FALSE because the current token
1169  * is not a redirection. Unparse the token and refetch it. */
1170  break;
1171  }
1172  bSuccess = TRUE;
1173 
1175  }
1176  if (CurrentTokenType == TOK_END)
1177  break;
1178 
1179  /* Unparse the current token, and reparse it below with no separators */
1180  UnParseToken();
1181 
1182  /* If bSuccess == FALSE, we know that it's still the old fetched token, but
1183  * it has been unparsed, so we need to refetch it before quitting the loop. */
1184  if (!bSuccess)
1185  {
1186  ParseToken(0, NULL);
1187  break;
1188  }
1189  }
1190 #endif
1191 
1192  ParseToken(0, NULL);
1193  }
1194  *Pos = _T('\0');
1195 
1197  ParsedLine,
1198  ParsedLine + TailOffset);
1199  if (!Cmd)
1200  {
1201  WARN("Cannot allocate memory for Cmd!\n");
1202  ParseError();
1203  return NULL;
1204  }
1205  return Cmd;
1206 }
1207 
1208 static PARSED_COMMAND*
1210 {
1211  PARSED_COMMAND* Cmd = NULL;
1212  REDIRECTION* RedirList = NULL;
1213 
1214  /* In this context, '@' is considered as a separate token */
1215  if ((*CurrentToken == _T('@')) && (CurrentTokenType == TOK_OPERATOR))
1216  {
1218  if (!Cmd)
1219  {
1220  WARN("Cannot allocate memory for Cmd!\n");
1221  ParseError();
1222  return NULL;
1223  }
1224  /* @ acts like a unary operator with low precedence,
1225  * so call the top-level parser */
1226  Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
1227  return Cmd;
1228  }
1229 
1230  /* Process leading redirections and get the head of the command */
1231 #ifndef MSCMD_REDIR_PARSE_BUGS
1233  {
1234  if (!ParseRedirection(&RedirList))
1235  return NULL;
1236 
1237  ParseToken(_T('('), STANDARD_SEPS);
1238  }
1239 #else
1240  {
1241  BOOL bSuccess = FALSE;
1242  while (CurrentTokenType != TOK_END)
1243  {
1244  if (!ParseRedirection(&RedirList))
1245  {
1246  /* If an actual error happened in ParseRedirection(), bail out */
1247  if (bParseError)
1248  return NULL;
1249 
1250  /* Otherwise it just returned FALSE because
1251  * the current token is not a redirection. */
1252  break;
1253  }
1254  bSuccess = TRUE;
1255 
1257  }
1258  if (bSuccess)
1259  {
1260  /* Unparse the current token, and reparse it with support for parenthesis */
1261  if (CurrentTokenType != TOK_END)
1262  UnParseToken();
1263 
1264  ParseToken(_T('('), STANDARD_SEPS);
1265  }
1266  }
1267 #endif
1268 
1270  Cmd = ParseCommandPart(&RedirList);
1271  else if (CurrentTokenType == TOK_BEGIN_BLOCK)
1272  Cmd = ParseBlock(&RedirList);
1273  else if (CurrentTokenType == TOK_END_BLOCK && !RedirList)
1274  return NULL;
1275 
1276  if (Cmd)
1277  {
1278  /* FOR and IF commands cannot have leading redirection
1279  * (checked by ParseCommandPart(), errors out if so). */
1280  ASSERT(!RedirList || (Cmd->Type != C_FOR && Cmd->Type != C_IF));
1281 
1282  /* Save the redirection list in the command */
1283  Cmd->Redirections = RedirList;
1284 
1285  /* Return the new command */
1286  return Cmd;
1287  }
1288 
1289  ParseError();
1290  FreeRedirection(RedirList);
1291  return NULL;
1292 }
1293 
1294 static PARSED_COMMAND*
1296  IN COMMAND_TYPE OpType)
1297 {
1299 
1300  if (OpType == C_OP_LOWEST) // i.e. CP_MULTI
1301  {
1302  /* Ignore any parser-level comments */
1303  if (bIgnoreParserComments && (*CurrentToken == _T(':')))
1304  {
1305  /* Ignore the rest of the line, including line continuations */
1306  while (ParseToken(0, NULL) != TOK_END)
1307  ;
1308 #ifdef MSCMD_PARENS_PARSE_BUGS
1309  /*
1310  * Return NULL in case we are NOT inside a parenthesized block,
1311  * otherwise continue. The effects can be observed as follows:
1312  * within a parenthesized block, every second ':'-prefixed command
1313  * is not ignored, while the first of each "pair" is ignored.
1314  * This first command **MUST NOT** be followed by an empty line,
1315  * otherwise a syntax error is raised.
1316  */
1317  if (InsideBlock == 0)
1318  {
1319 #endif
1320  return NULL;
1321 #ifdef MSCMD_PARENS_PARSE_BUGS
1322  }
1323  /* Get the next token */
1324  ParseToken(0, NULL);
1325 #endif
1326  }
1327 
1328  /*
1329  * Ignore single closing parenthesis outside of command blocks,
1330  * thus interpreted as a command. This very specific situation
1331  * can happen e.g. while running in batch mode, when jumping to
1332  * a label present inside a command block.
1333  *
1334  * NOTE: If necessary, this condition can be restricted to only
1335  * when a batch context 'bc' is active.
1336  *
1337  * NOTE 2: For further security, Windows checks that we are NOT
1338  * currently inside a parenthesized block, and also, ignores
1339  * explicitly everything (ParseToken() loop) on the same line
1340  * (including line continuations) after this closing parenthesis.
1341  *
1342  * Why doing so? Consider the following batch:
1343  *
1344  * IF 1==1 (
1345  * :label
1346  * echo A
1347  * ) ^
1348  * ELSE (
1349  * echo B
1350  * exit /b
1351  * )
1352  * GOTO :label
1353  *
1354  * First the IF block is executed. Since the condition is trivially
1355  * true, only the first block "echo A" is executed, then execution
1356  * goes after the IF block, that is, at the GOTO. Here, the GOTO
1357  * jumps within the first IF-block, however, the running context now
1358  * is NOT an IF. So parsing and execution will go through each command,
1359  * starting with 'echo A'. But then one gets the ') ^\n ELSE (' part !!
1360  * If we want to make sense of this without bailing out due to
1361  * parsing error, we should ignore this line, **including** the line
1362  * continuation. Hence we need to loop over all the tokens following
1363  * the closing parenthesis, instead of just returning NULL straight ahead.
1364  * Then execution continues with the other commands, 'echo B' and
1365  * 'exit /b' (here to stop the code loop). Execution would also
1366  * continue (if 'exit' was replaced by something else) and encounter
1367  * the lone closing parenthesis ')', that should again be ignored.
1368  *
1369  * Note that this feature has been introduced in Win2k+.
1370  */
1371  if ( (_tcscmp(CurrentToken, _T(")")) == 0) &&
1373  {
1374  ASSERT(InsideBlock == 0);
1375 
1376  /* Ignore the rest of the line, including line continuations */
1377  while (ParseToken(0, NULL) != TOK_END)
1378  ;
1379  return NULL;
1380  }
1381 
1382 #ifdef MSCMD_PARENS_PARSE_BUGS
1383  /* Check whether we have an empty line only if we are not inside
1384  * a parenthesized block, and return NULL if so, otherwise do not
1385  * do anything; a syntax error will be raised later. */
1386  if (InsideBlock == 0)
1387 #endif
1388  if (!*CurrentToken || *CurrentToken == _T('\n'))
1389  {
1391  return NULL;
1392  }
1393  }
1394 
1395  if (OpType == C_OP_HIGHEST)
1396  Cmd = ParsePrimary();
1397  else
1398  Cmd = ParseCommandBinaryOp(OpType + 1);
1399 
1400  if (Cmd && !_tcscmp(CurrentToken, OpString[OpType - C_OP_LOWEST]))
1401  {
1402  PARSED_COMMAND* Left = Cmd;
1403  PARSED_COMMAND* Right;
1404 
1405  Right = ParseCommandOp(OpType);
1406  if (!Right)
1407  {
1408  /*
1409  * The '&' operator is allowed to have an empty RHS.
1410  * In this case, we directly return the LHS only.
1411  * Note that Windows' CMD prefers building a '&'
1412  * command with an empty RHS.
1413  */
1414  if (!bParseError && (OpType != C_MULTI))
1415  ParseError();
1416  if (bParseError)
1417  {
1418  FreeCommand(Left);
1419  return NULL;
1420  }
1421 
1422 #ifndef MSCMD_MULTI_EMPTY_RHS
1423  return Left;
1424 #endif
1425  }
1426 
1427  Cmd = AllocCommand(OpType, NULL, NULL);
1428  if (!Cmd)
1429  {
1430  WARN("Cannot allocate memory for Cmd!\n");
1431  ParseError();
1432  FreeCommand(Left);
1433  FreeCommand(Right);
1434  return NULL;
1435  }
1436  Cmd->Subcommands = Left;
1437  Left->Next = Right;
1438 #ifdef MSCMD_MULTI_EMPTY_RHS
1439  if (Right)
1440 #endif
1441  Right->Next = NULL;
1442  }
1443 
1444  return Cmd;
1445 }
1446 static __inline PARSED_COMMAND*
1448  IN COMMAND_TYPE OpType)
1449 {
1450  /* Start parsing: initialize the first token */
1451 
1452  /* Parse the prefix "quiet" operator '@' as a separate command.
1453  * Thus, @@foo@bar is parsed as: '@', '@', 'foo@bar'. */
1455 
1456  return ParseCommandBinaryOp(OpType);
1457 }
1458 
1459 
1462  IN PCTSTR Line)
1463 {
1465 
1466  if (Line)
1467  {
1468  if (!SubstituteVars(Line, ParseLine, _T('%')))
1469  return NULL;
1471  }
1472  else
1473  {
1474  if (!ReadLine(ParseLine, FALSE))
1475  return NULL;
1477  }
1478 
1479  InitParser();
1480 
1482  if (Cmd)
1483  {
1484  bIgnoreEcho = FALSE;
1485 
1486  if ((CurrentTokenType != TOK_END) &&
1487  (_tcscmp(CurrentToken, _T("\n")) != 0))
1488  {
1489  ParseError();
1490  }
1491  if (bParseError)
1492  {
1493  FreeCommand(Cmd);
1494  return NULL;
1495  }
1496 
1497  /* Debugging support */
1498  if (fDumpParse)
1499  DumpCommand(Cmd, 0);
1500  }
1501  else
1502  {
1503  bIgnoreEcho = TRUE;
1504  }
1505  return Cmd;
1506 }
1507 
1508 
1509 /*
1510  * This function is similar to EchoCommand(), but is used
1511  * for dumping the command tree for debugging purposes.
1512  */
1513 static VOID
1515  IN REDIRECTION* Redirections)
1516 {
1517  REDIRECTION* Redir;
1518 
1519  if (Redirections)
1520 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1521  ConOutPuts(_T(" Redir: "));
1522 #else
1523  ConOutPuts(_T("Redir: "));
1524 #endif
1525  for (Redir = Redirections; Redir; Redir = Redir->Next)
1526  {
1527  ConOutPrintf(_T(" %x %s%s"), Redir->Number,
1528  RedirString[Redir->Mode], Redir->Filename);
1529  }
1530 }
1531 
1532 VOID
1535  IN ULONG SpacePad)
1536 {
1537 /*
1538  * This macro is like DumpCommand(Cmd, Pad);
1539  * but avoids an extra recursive level.
1540  * Note that it can be used ONLY for terminating commands!
1541  */
1542 #define DUMP(Command, Pad) \
1543 do { \
1544  Cmd = (Command); \
1545  SpacePad = (Pad); \
1546  goto dump; \
1547 } while (0)
1548 
1549  PARSED_COMMAND* Sub;
1550 
1551 dump:
1552  if (!Cmd)
1553  return;
1554 
1555  /* Space padding */
1556  ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1557 
1558  switch (Cmd->Type)
1559  {
1560  case C_COMMAND:
1561  case C_REM:
1562  {
1563  /* Generic command name, and Type */
1564 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1565  ConOutPrintf(_T("Cmd: %s Type: %x"),
1566  Cmd->Command.First, Cmd->Type);
1567 #else
1568  ConOutPrintf(_T("Cmd: %s Type: %x "),
1569  Cmd->Command.First, Cmd->Type);
1570 #endif
1571  /* Arguments */
1572  if (Cmd->Command.Rest && *(Cmd->Command.Rest))
1573 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1574  ConOutPrintf(_T(" Args: `%s'"), Cmd->Command.Rest);
1575 #else
1576  ConOutPrintf(_T("Args: `%s' "), Cmd->Command.Rest);
1577 #endif
1578  /* Redirections */
1579  DumpRedir(Cmd->Redirections);
1580 
1581  ConOutChar(_T('\n'));
1582  return;
1583  }
1584 
1585  case C_QUIET:
1586  {
1587 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1588  ConOutChar(_T('@'));
1589 #else
1590  ConOutPuts(_T("@ "));
1591 #endif
1592  DumpRedir(Cmd->Redirections); // FIXME: Can we have leading redirections??
1593  ConOutChar(_T('\n'));
1594 
1595  /*DumpCommand*/DUMP(Cmd->Subcommands, SpacePad + 2);
1596  return;
1597  }
1598 
1599  case C_BLOCK:
1600  {
1601 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1602  ConOutChar(_T('('));
1603 #else
1604  ConOutPuts(_T("( "));
1605 #endif
1606  DumpRedir(Cmd->Redirections);
1607  ConOutChar(_T('\n'));
1608 
1609  SpacePad += 2;
1610 
1611  for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
1612  {
1613 #if defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
1614  /*
1615  * We will emulate Windows' CMD handling of "CRLF" and "&" multi-command
1616  * enumeration within parenthesized command blocks.
1617  */
1618 
1619  if (!Sub->Next)
1620  {
1621  DumpCommand(Sub, SpacePad);
1622  continue;
1623  }
1624 
1625  if (Sub->Type != C_MULTI)
1626  {
1627  ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1628  ConOutPuts(_T("CRLF \n"));
1629  DumpCommand(Sub, SpacePad);
1630  continue;
1631  }
1632 
1633  /* Now, Sub->Type == C_MULTI */
1634 
1635  Cmd = Sub;
1636 
1637  ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1638  ConOutPrintf(_T("%s \n"), OpString[Cmd->Type - C_OP_LOWEST]);
1639  // FIXME: Can we have redirections on these operator-type commands?
1640 
1641  SpacePad += 2;
1642 
1643  Cmd = Cmd->Subcommands;
1644  DumpCommand(Cmd, SpacePad);
1645  ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1646  ConOutPuts(_T("CRLF \n"));
1647  DumpCommand(Cmd->Next, SpacePad);
1648 
1649  // NOTE: Next commands will remain indented.
1650 
1651 #else
1652 
1653  /*
1654  * If this command is followed by another one, first display "CRLF".
1655  * This also emulates the CRLF placement "bug" of Windows' CMD
1656  * for the last two commands.
1657  */
1658  if (Sub->Next)
1659  {
1660  ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1661 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1662  ConOutPuts(_T("CRLF\n"));
1663 #else
1664  ConOutPuts(_T("CRLF \n"));
1665 #endif
1666  }
1667  DumpCommand(Sub, SpacePad);
1668 
1669 #endif // defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
1670  }
1671 
1672  return;
1673  }
1674 
1675  case C_MULTI:
1676  case C_OR:
1677  case C_AND:
1678  case C_PIPE:
1679  {
1680 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1681  ConOutPrintf(_T("%s\n"), OpString[Cmd->Type - C_OP_LOWEST]);
1682 #else
1683  ConOutPrintf(_T("%s \n"), OpString[Cmd->Type - C_OP_LOWEST]);
1684 #endif
1685  // FIXME: Can we have redirections on these operator-type commands?
1686 
1687  SpacePad += 2;
1688 
1689  Sub = Cmd->Subcommands;
1690  DumpCommand(Sub, SpacePad);
1691  /*DumpCommand*/DUMP(Sub->Next, SpacePad);
1692  return;
1693  }
1694 
1695  case C_FOR:
1696  {
1697  ConOutPuts(_T("for"));
1698  /* NOTE: FOR cannot have leading redirections */
1699 
1700  if (Cmd->For.Switches & FOR_DIRS) ConOutPuts(_T(" /D"));
1701  if (Cmd->For.Switches & FOR_F) ConOutPuts(_T(" /F"));
1702  if (Cmd->For.Switches & FOR_LOOP) ConOutPuts(_T(" /L"));
1703  if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPuts(_T(" /R"));
1704  if (Cmd->For.Params)
1705  ConOutPrintf(_T(" %s"), Cmd->For.Params);
1706  ConOutPrintf(_T(" %%%c in (%s) do\n"), Cmd->For.Variable, Cmd->For.List);
1707  /*DumpCommand*/DUMP(Cmd->Subcommands, SpacePad + 2);
1708  return;
1709  }
1710 
1711  case C_IF:
1712  {
1713  ConOutPuts(_T("if"));
1714  /* NOTE: IF cannot have leading redirections */
1715 
1716  if (Cmd->If.Flags & IFFLAG_IGNORECASE)
1717  ConOutPuts(_T(" /I"));
1718 
1719  ConOutChar(_T('\n'));
1720 
1721  SpacePad += 2;
1722 
1723  /*
1724  * Show the IF command condition as a command.
1725  * If it is negated, indent the command more.
1726  */
1727  if (Cmd->If.Flags & IFFLAG_NEGATE)
1728  {
1729  ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1730  ConOutPuts(_T("not\n"));
1731  SpacePad += 2;
1732  }
1733 
1734  ConOutPrintf(_T("%*s"), SpacePad, _T(""));
1735 
1736  /*
1737  * Command name:
1738  * - Unary operator: its name is the command name, and its argument is the command argument.
1739  * - Binary operator: its LHS is the command name, its RHS is the command argument.
1740  *
1741  * Type:
1742  * Windows' CMD (Win2k3 / Win7-10) values are as follows:
1743  * CMDEXTVERSION Type: 0x32 / 0x34
1744  * ERRORLEVEL Type: 0x33 / 0x35
1745  * DEFINED Type: 0x34 / 0x36
1746  * EXIST Type: 0x35 / 0x37
1747  * == Type: 0x37 / 0x39 (String Comparison)
1748  *
1749  * For the following command:
1750  * NOT Type: 0x36 / 0x38
1751  * Windows only prints it without any type / redirection.
1752  *
1753  * For the following command:
1754  * EQU, NEQ, etc. Type: 0x38 / 0x3a (Generic Comparison)
1755  * Windows displays it as command of unknown type.
1756  */
1757 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1758  ConOutPrintf(_T("Cmd: %s Type: %x"),
1759  (Cmd->If.Operator <= IF_MAX_UNARY) ?
1760  IfOperatorString[Cmd->If.Operator] :
1761  Cmd->If.LeftArg,
1762  Cmd->If.Operator);
1763 #else
1764  ConOutPrintf(_T("Cmd: %s Type: %x "),
1765  (Cmd->If.Operator <= IF_MAX_UNARY) ?
1766  IfOperatorString[Cmd->If.Operator] :
1767  Cmd->If.LeftArg,
1768  Cmd->If.Operator);
1769 #endif
1770  /* Arguments */
1771 #ifndef MSCMD_ECHO_COMMAND_COMPAT
1772  ConOutPrintf(_T(" Args: `%s'"), Cmd->If.RightArg);
1773 #else
1774  ConOutPrintf(_T("Args: `%s' "), Cmd->If.RightArg);
1775 #endif
1776 
1777  ConOutChar(_T('\n'));
1778 
1779  if (Cmd->If.Flags & IFFLAG_NEGATE)
1780  {
1781  SpacePad -= 2;
1782  }
1783 
1784  Sub = Cmd->Subcommands;
1785  DumpCommand(Sub, SpacePad);
1786  if (Sub->Next)
1787  {
1788  ConOutPrintf(_T("%*s"), SpacePad - 2, _T(""));
1789  ConOutPuts(_T("else\n"));
1790  DumpCommand(Sub->Next, SpacePad);
1791  }
1792  return;
1793  }
1794 
1795  default:
1796  ConOutPrintf(_T("*** Unknown type: %x\n"), Cmd->Type);
1797  break;
1798  }
1799 
1800 #undef DUMP
1801 }
1802 
1803 /*
1804  * Reconstruct a parse tree into text form; used for echoing
1805  * batch file commands and FOR instances.
1806  */
1807 VOID
1810 {
1811  PARSED_COMMAND* Sub;
1812  REDIRECTION* Redir;
1813 
1814  if (!Cmd)
1815  return;
1816 
1817  switch (Cmd->Type)
1818  {
1819  case C_COMMAND:
1820  case C_REM:
1821  {
1822  if (SubstituteForVars(Cmd->Command.First, TempBuf))
1823  ConOutPrintf(_T("%s"), TempBuf);
1824  if (SubstituteForVars(Cmd->Command.Rest, TempBuf))
1825  {
1826  ConOutPrintf(_T("%s"), TempBuf);
1827 #ifdef MSCMD_ECHO_COMMAND_COMPAT
1828  /* NOTE: For Windows compatibility, add a trailing space after printing the command parameter, if present */
1829  if (*TempBuf) ConOutChar(_T(' '));
1830 #endif
1831  }
1832  break;
1833  }
1834 
1835  case C_QUIET:
1836  return;
1837 
1838  case C_BLOCK:
1839  {
1840  BOOLEAN bIsFirstCmdCRLF;
1841 
1842  ConOutChar(_T('('));
1843 
1844  Sub = Cmd->Subcommands;
1845 
1846  bIsFirstCmdCRLF = (Sub && Sub->Next);
1847 
1848 #if defined(MSCMD_ECHO_COMMAND_COMPAT) && defined(MSCMD_PARSER_BUGS)
1849  /*
1850  * We will emulate Windows' CMD handling of "CRLF" and "&" multi-command
1851  * enumeration within parenthesized command blocks.
1852  */
1853  bIsFirstCmdCRLF = bIsFirstCmdCRLF && (Sub->Type != C_MULTI);
1854 #endif
1855 
1856  /*
1857  * Single-command block: display all on one line.
1858  * Multi-command block: display commands on separate lines.
1859  */
1860  if (bIsFirstCmdCRLF)
1861  ConOutChar(_T('\n'));
1862 
1863  for (; Sub; Sub = Sub->Next)
1864  {
1865  EchoCommand(Sub);
1866  if (Sub->Next)
1867 #ifdef MSCMD_ECHO_COMMAND_COMPAT
1868  ConOutPuts(_T(" \n "));
1869 #else
1870  ConOutChar(_T('\n'));
1871 #endif
1872  }
1873 
1874  if (bIsFirstCmdCRLF)
1875  ConOutChar(_T('\n'));
1876 
1877 #ifdef MSCMD_ECHO_COMMAND_COMPAT
1878  /* NOTE: For Windows compatibility, add a trailing space after printing the closing parenthesis */
1879  ConOutPuts(_T(") "));
1880 #else
1881  ConOutChar(_T(')'));
1882 #endif
1883  break;
1884  }
1885 
1886  case C_MULTI:
1887  case C_OR:
1888  case C_AND:
1889  case C_PIPE:
1890  {
1891  Sub = Cmd->Subcommands;
1892  EchoCommand(Sub);
1893  ConOutPrintf(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
1894  EchoCommand(Sub->Next);
1895  break;
1896  }
1897 
1898  case C_FOR:
1899  {
1900  ConOutPuts(_T("for"));
1901  if (Cmd->For.Switches & FOR_DIRS) ConOutPuts(_T(" /D"));
1902  if (Cmd->For.Switches & FOR_F) ConOutPuts(_T(" /F"));
1903  if (Cmd->For.Switches & FOR_LOOP) ConOutPuts(_T(" /L"));
1904  if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPuts(_T(" /R"));
1905  if (Cmd->For.Params)
1906  ConOutPrintf(_T(" %s"), Cmd->For.Params);
1907  if (Cmd->For.List && SubstituteForVars(Cmd->For.List, TempBuf))
1908  ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, TempBuf);
1909  else
1910  ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
1911  EchoCommand(Cmd->Subcommands);
1912  break;
1913  }
1914 
1915  case C_IF:
1916  {
1917  ConOutPuts(_T("if"));
1918  if (Cmd->If.Flags & IFFLAG_IGNORECASE)
1919  ConOutPuts(_T(" /I"));
1920  if (Cmd->If.Flags & IFFLAG_NEGATE)
1921  ConOutPuts(_T(" not"));
1922  if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, TempBuf))
1923  ConOutPrintf(_T(" %s"), TempBuf);
1924  ConOutPrintf(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
1925  if (SubstituteForVars(Cmd->If.RightArg, TempBuf))
1926  ConOutPrintf(_T(" %s "), TempBuf);
1927  Sub = Cmd->Subcommands;
1928  EchoCommand(Sub);
1929  if (Sub->Next)
1930  {
1931  ConOutPuts(_T(" else "));
1932  EchoCommand(Sub->Next);
1933  }
1934  break;
1935  }
1936 
1937  default:
1938  ASSERT(FALSE);
1939  break;
1940  }
1941 
1942  for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
1943  {
1944  if (SubstituteForVars(Redir->Filename, TempBuf))
1945  {
1946 #ifdef MSCMD_ECHO_COMMAND_COMPAT
1947  ConOutPrintf(_T("%c%s%s "),
1948  _T('0') + Redir->Number,
1949  RedirString[Redir->Mode], TempBuf);
1950 #else
1951  ConOutPrintf(_T(" %c%s%s"),
1952  _T('0') + Redir->Number,
1953  RedirString[Redir->Mode], TempBuf);
1954 #endif
1955  }
1956  }
1957 }
1958 
1959 /*
1960  * "Unparse" a command into a text form suitable for passing to CMD /C.
1961  * Used for pipes. This is basically the same thing as EchoCommand(),
1962  * but writing into a string instead of to standard output.
1963  */
1964 PTCHAR
1967  OUT PTCHAR Out,
1968  IN PTCHAR OutEnd)
1969 {
1970 /*
1971  * Since this function has the annoying requirement that it must avoid
1972  * overflowing the supplied buffer, define some helper macros to make
1973  * this less painful.
1974  */
1975 #define CHAR(Char) \
1976 do { \
1977  if (Out == OutEnd) return NULL; \
1978  *Out++ = Char; \
1979 } while (0)
1980 #define STRING(String) \
1981 do { \
1982  if (Out + _tcslen(String) > OutEnd) return NULL; \
1983  Out = _stpcpy(Out, String); \
1984 } while (0)
1985 #define PRINTF(Format, ...) \
1986 do { \
1987  UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \
1988  if (Len > (UINT)(OutEnd - Out)) return NULL; \
1989  Out += Len; \
1990 } while (0)
1991 #define RECURSE(Subcommand) \
1992 do { \
1993  Out = UnparseCommand(Subcommand, Out, OutEnd); \
1994  if (!Out) return NULL; \
1995 } while (0)
1996 
1997  PARSED_COMMAND* Sub;
1998  REDIRECTION* Redir;
1999 
2000  if (!Cmd)
2001  return Out;
2002 
2003  switch (Cmd->Type)
2004  {
2005  case C_COMMAND:
2006  case C_REM:
2007  {
2008  /* This is fragile since there could be special characters, but
2009  * Windows doesn't bother escaping them, so for compatibility
2010  * we probably shouldn't do it either */
2011  if (!SubstituteForVars(Cmd->Command.First, TempBuf)) return NULL;
2012  STRING(TempBuf);
2013  if (!SubstituteForVars(Cmd->Command.Rest, TempBuf)) return NULL;
2014  STRING(TempBuf);
2015  break;
2016  }
2017 
2018  case C_QUIET:
2019  {
2020  CHAR(_T('@'));
2021  RECURSE(Cmd->Subcommands);
2022  break;
2023  }
2024 
2025  case C_BLOCK:
2026  {
2027  CHAR(_T('('));
2028  for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
2029  {
2030  RECURSE(Sub);
2031  if (Sub->Next)
2032  CHAR(_T('&'));
2033  }
2034  CHAR(_T(')'));
2035  break;
2036  }
2037 
2038  case C_MULTI:
2039  case C_OR:
2040  case C_AND:
2041  case C_PIPE:
2042  {
2043  Sub = Cmd->Subcommands;
2044  RECURSE(Sub);
2045  PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
2046  RECURSE(Sub->Next);
2047  break;
2048  }
2049 
2050  case C_FOR:
2051  {
2052  STRING(_T("for"));
2053  if (Cmd->For.Switches & FOR_DIRS) STRING(_T(" /D"));
2054  if (Cmd->For.Switches & FOR_F) STRING(_T(" /F"));
2055  if (Cmd->For.Switches & FOR_LOOP) STRING(_T(" /L"));
2056  if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R"));
2057  if (Cmd->For.Params)
2058  PRINTF(_T(" %s"), Cmd->For.Params);
2059  if (Cmd->For.List && SubstituteForVars(Cmd->For.List, TempBuf))
2060  PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, TempBuf);
2061  else
2062  PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
2063  RECURSE(Cmd->Subcommands);
2064  break;
2065  }
2066 
2067  case C_IF:
2068  {
2069  STRING(_T("if"));
2070  if (Cmd->If.Flags & IFFLAG_IGNORECASE)
2071  STRING(_T(" /I"));
2072  if (Cmd->If.Flags & IFFLAG_NEGATE)
2073  STRING(_T(" not"));
2074  if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, TempBuf))
2075  PRINTF(_T(" %s"), TempBuf);
2076  PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
2077  if (!SubstituteForVars(Cmd->If.RightArg, TempBuf)) return NULL;
2078  PRINTF(_T(" %s "), TempBuf);
2079  Sub = Cmd->Subcommands;
2080  RECURSE(Sub);
2081  if (Sub->Next)
2082  {
2083  STRING(_T(" else "));
2084  RECURSE(Sub->Next);
2085  }
2086  break;
2087  }
2088 
2089  default:
2090  ASSERT(FALSE);
2091  break;
2092  }
2093 
2094  for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
2095  {
2096  if (!SubstituteForVars(Redir->Filename, TempBuf))
2097  return NULL;
2098  PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number,
2099  RedirString[Redir->Mode], TempBuf);
2100  }
2101  return Out;
2102 
2103 #undef CHAR
2104 #undef STRING
2105 #undef PRINTF
2106 #undef RECURSE
2107 }
#define FOR_DIRS
Definition: cmd.h:234
static TOK_TYPE CurrentTokenType
Definition: parser.c:100
#define _istdigit
Definition: tchar.h:1494
#define IN
Definition: typedefs.h:39
Definition: cmd.h:359
static PARSED_COMMAND * ParseRem(VOID)
Definition: parser.c:1003
VOID EchoCommand(IN PARSED_COMMAND *Cmd)
Definition: parser.c:1808
BOOL SubstituteVars(IN PCTSTR Src, OUT PTSTR Dest, IN TCHAR Delim)
Definition: cmd.c:1516
static INT InsideBlock
Definition: parser.c:105
BOOL bIgnoreEcho
Definition: cmd.c:155
LPCSTR PCTSTR
Definition: ntbasedef.h:488
#define RECURSE(Subcommand)
#define _tcsicmp
Definition: xmlstorage.h:205
COMMAND_TYPE Type
Definition: cmd.h:374
enum _REDIR_MODE REDIR_MODE
#define IFFLAG_IGNORECASE
Definition: cmd.h:261
static TCHAR CurrentToken[CMDLINE_LENGTH]
Definition: parser.c:99
int _tcscmp(const _TCHAR *s1, const _TCHAR *s2)
Definition: tcscmp.h:8
#define TRUE
Definition: types.h:120
VOID ConOutChar(TCHAR c)
Definition: console.c:123
#define IF_MAX_UNARY
Definition: cmd.h:361
static PARSED_COMMAND * ParseFor(VOID)
Definition: parser.c:875
static PARSED_COMMAND * AllocCommand(IN COMMAND_TYPE Type, IN PCTSTR CmdHead OPTIONAL, IN PCTSTR CmdTail OPTIONAL)
Definition: parser.c:459
#define WARN(fmt,...)
Definition: debug.h:112
ush Pos
Definition: deflate.h:92
#define CMDLINE_LENGTH
Definition: help.h:12
Definition: cmd.h:361
#define INVALID_HANDLE_VALUE
Definition: compat.h:590
_TCHAR * _tcscpy(_TCHAR *to, const _TCHAR *from)
Definition: tcscpy.h:8
static __inline PARSED_COMMAND * ParseCommandOp(IN COMMAND_TYPE OpType)
Definition: parser.c:1447
BOOL bIgnoreParserComments
Definition: parser.c:96
#define ConOutPrintf(szStr,...)
Definition: console.h:42
PTCHAR UnparseCommand(IN PARSED_COMMAND *Cmd, OUT PTCHAR Out, IN PTCHAR OutEnd)
Definition: parser.c:1965
Definition: shell.h:41
static VOID StripQuotes(LPSTR in)
Definition: cmdcons.c:116
int32_t INT
Definition: typedefs.h:58
static BOOLEAN bSuccess
Definition: drive.cpp:432
#define FOR_F
Definition: cmd.h:235
Definition: cmd.h:355
static PARSED_COMMAND * ParseIf(VOID)
Definition: parser.c:780
struct _PARSED_COMMAND * Next
Definition: cmd.h:372
IN PVCB IN PBCB OUT PDIRENT IN USHORT IN POEM_STRING Filename
Definition: fatprocs.h:934
static PARSED_COMMAND * ParseCommandPart(IN OUT REDIRECTION **RedirList)
Definition: parser.c:1051
#define C_OP_LOWEST
Definition: parser.c:38
REDIR_MODE Mode
Definition: cmd.h:453
static BOOL bLineContinuations
Definition: parser.c:91
HANDLE OldHandle
Definition: cmd.h:451
VOID FreeCommand(IN OUT PARSED_COMMAND *Cmd)
Definition: parser.c:527
Definition: cmd.h:359
static VOID DumpRedir(IN REDIRECTION *Redirections)
Definition: parser.c:1514
LPTSTR _stpcpy(LPTSTR, LPCTSTR)
Definition: misc.c:460
Definition: cmd.h:353
#define PRINTF(Format,...)
BOOL bParseError
Definition: parser.c:90
#define FALSE
Definition: types.h:117
unsigned int BOOL
Definition: ntddk_ex.h:94
#define _tcsnicmp
Definition: xmlstorage.h:207
static PTCHAR OldParsePos
Definition: parser.c:94
static BOOL ParseRedirection(IN OUT REDIRECTION **List)
Definition: parser.c:551
#define FOR_LOOP
Definition: cmd.h:236
static PARSED_COMMAND * ParseCommandBinaryOp(IN COMMAND_TYPE OpType)
Definition: parser.c:1295
static TOK_TYPE ParseTokenEx(IN TCHAR PrefixOperator OPTIONAL, IN TCHAR ExtraEnd OPTIONAL, IN PCTSTR Separators OPTIONAL, IN BOOL bHandleContinuations)
Definition: parser.c:241
size_t __cdecl _tcslen(const _TCHAR *str)
Definition: tcslen.h:9
unsigned char BOOLEAN
enum _TOK_TYPE TOK_TYPE
CHAR * PTSTR
Definition: xmlstorage.h:191
_TCHAR * _tcschr(const _TCHAR *s, _XINT c)
Definition: tcschr.h:4
static TCHAR ParseChar(VOID)
Definition: parser.c:187
#define _tcsstr
Definition: xmlstorage.h:199
void restart(int argc, const char *argv[])
Definition: cmds.c:2115
BYTE Number
Definition: cmd.h:452
char TCHAR
Definition: xmlstorage.h:189
#define _T(x)
Definition: vfdio.h:22
BOOL SubstituteForVars(IN PCTSTR Src, OUT PTSTR Dest)
Definition: cmd.c:1598
#define ASSERT(a)
Definition: mode.c:44
TCHAR ParseLine[CMDLINE_LENGTH]
Definition: parser.c:92
static VOID InitParser(VOID)
Definition: parser.c:173
static PTCHAR ParsePos
Definition: parser.c:93
Definition: cmd.h:359
static __inline INT ParseToken(IN TCHAR ExtraEnd OPTIONAL, IN PCTSTR Separators OPTIONAL)
Definition: parser.c:450
#define IFFLAG_NEGATE
Definition: cmd.h:260
TCHAR Filename[]
Definition: cmd.h:454
Type
Definition: Type.h:6
DWORD error
Definition: parser.c:105
static const TCHAR *const IfOperatorString[]
Definition: parser.c:44
#define STRING(String)
BOOL ReadLine(TCHAR *commandline, BOOL bMore)
Definition: cmd.c:1723
VOID ParseErrorEx(IN PCTSTR s)
Definition: parser.c:227
Definition: ncftp.h:79
#define FOR_RECURSIVE
Definition: cmd.h:237
_TOK_TYPE
Definition: parser.c:77
static const TCHAR RedirString[][3]
Definition: parser.c:42
Definition: sacdrv.h:277
BOOL bHandleContinuations
Definition: parser.c:97
#define VOID
Definition: acefi.h:82
_Must_inspect_result_ _In_ WDFCMRESLIST List
Definition: wdfresource.h:550
#define memcpy(s1, s2, n)
Definition: mkisofs.h:878
GLdouble s
Definition: gl.h:2039
#define STANDARD_SEPS
Definition: cmd.h:348
VOID DumpCommand(IN PARSED_COMMAND *Cmd, IN ULONG SpacePad)
Definition: parser.c:1533
_In_opt_ PENTER_STATE_SYSTEM_HANDLER _In_opt_ PVOID _In_ LONG _In_opt_ LONG volatile * Number
Definition: ntpoapi.h:204
unsigned char BYTE
Definition: xxhash.c:193
#define DUMP(Command, Pad)
Definition: cmd.h:361
enum _COMMAND_TYPE COMMAND_TYPE
#define ERR(fmt,...)
Definition: debug.h:110
ULONG_PTR SIZE_T
Definition: typedefs.h:80
#define cmd_alloc(size)
Definition: cmddbg.h:29
#define cmd_free(ptr)
Definition: cmddbg.h:31
BOOL bEnableExtensions
Definition: more.c:53
Definition: parser.c:79
Definition: cmd.h:357
void(* Func)(int)
BOOLEAN fDumpTokens
Definition: parser.c:35
#define cmd_dup(str)
Definition: cmddbg.h:32
static __inline BOOL IsSeparator(TCHAR Char)
Definition: parser.c:72
#define FIELD_OFFSET(t, f)
Definition: typedefs.h:255
static VOID ResetParser(IN PTCHAR Pos)
Definition: parser.c:107
static const TCHAR OpString[][3]
Definition: parser.c:40
#define NULL
Definition: types.h:112
VOID FreeRedirection(REDIRECTION *)
Definition: redir.c:153
struct _REDIRECTION * Next
Definition: cmd.h:450
Definition: cmd.h:359
#define OUT
Definition: typedefs.h:40
#define _istspace
Definition: tchar.h:1504
unsigned int ULONG
Definition: retypes.h:1
#define IF_MAX_COMPARISON
PARSED_COMMAND * ParseCommand(IN PCTSTR Line)
Definition: parser.c:1461
static PARSED_COMMAND * ParsePrimary(VOID)
Definition: parser.c:1209
VOID error_syntax(PCTSTR s)
Definition: error.c:152
#define C_OP_HIGHEST
Definition: parser.c:39
static VOID UnParseToken(VOID)
Definition: parser.c:160
static TCHAR TempBuf[CMDLINE_LENGTH]
Definition: parser.c:88
#define memset(x, y, z)
Definition: compat.h:39
#define CHAR(Char)
static void dump(const void *ptr, unsigned len)
Definition: msc.c:95
BOOLEAN fDumpParse
Definition: parser.c:36
static __inline VOID ParseError(VOID)
Definition: parser.c:235
static PARSED_COMMAND * ParseBlock(IN OUT REDIRECTION **RedirList)
Definition: parser.c:649
char * PTCHAR
Definition: ntbasedef.h:476
Definition: cmd.h:274
VOID ConOutPuts(LPTSTR szText)
Definition: tee.c:27
PULONG MinorVersion OPTIONAL
Definition: CrossNt.h:68