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 */ 35 BOOLEAN fDumpTokens = FALSE; 36 BOOLEAN fDumpParse = FALSE; 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 { 46 /** Unary operators **/ 47 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 57 /** Binary operators **/ 58 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 { 79 TOK_END, 80 TOK_NORMAL, 81 TOK_OPERATOR, 82 TOK_REDIRECTION, 83 TOK_BEGIN_BLOCK, 84 TOK_END_BLOCK 85 } TOK_TYPE; 86 87 /* Scratch buffer for temporary command substitutions / expansions */ 88 static TCHAR TempBuf[CMDLINE_LENGTH]; 89 90 /*static*/ BOOL bParseError; 91 static BOOL bLineContinuations; 92 /*static*/ TCHAR ParseLine[CMDLINE_LENGTH]; 93 static PTCHAR ParsePos; 94 static PTCHAR OldParsePos; 95 96 BOOL bIgnoreParserComments = TRUE; 97 BOOL bHandleContinuations = TRUE; 98 99 static TCHAR CurrentToken[CMDLINE_LENGTH]; 100 static TOK_TYPE CurrentTokenType = TOK_END; 101 #ifndef MSCMD_PARSER_BUGS 102 static BOOL bReparseToken = FALSE; 103 static PTCHAR LastCurTokPos; 104 #endif 105 static INT InsideBlock = 0; 106 107 static VOID ResetParser(IN PTCHAR Pos) 108 { 109 bParseError = FALSE; 110 ParsePos = Pos; 111 OldParsePos = ParsePos; 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 */ 160 static VOID UnParseToken(VOID) 161 { 162 ParsePos = OldParsePos; 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 173 static VOID InitParser(VOID) 174 { 175 *CurrentToken = 0; 176 CurrentTokenType = TOK_END; 177 InsideBlock = 0; 178 179 #ifndef MSCMD_PARSER_BUGS 180 bReparseToken = FALSE; 181 LastCurTokPos = NULL; 182 #endif 183 184 ResetParser(ParseLine); 185 } 186 187 static TCHAR ParseChar(VOID) 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 { 219 ResetParser(ParseLine); 220 if (*ParsePos) 221 goto restart; 222 } 223 } 224 return Char; 225 } 226 227 VOID ParseErrorEx(IN PCTSTR s) 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 { 237 ParseErrorEx(CurrentTokenType != TOK_END ? CurrentToken : NULL); 238 } 239 240 static TOK_TYPE 241 ParseTokenEx( 242 IN TCHAR PrefixOperator OPTIONAL, 243 IN TCHAR ExtraEnd OPTIONAL, 244 IN PCTSTR Separators OPTIONAL, 245 IN BOOL bHandleContinuations) 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 */ 272 OldParsePos = 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 */ 336 if (CurrentTokenType != TOK_BEGIN_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 { 370 Type = TOK_BEGIN_BLOCK; 371 *Out++ = Char; 372 Char = ParseChar(); 373 } 374 else if (Char == _T(')')) 375 { 376 Type = TOK_END_BLOCK; 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 { 395 Type = TOK_REDIRECTION; 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 450 ParseToken( 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* 459 AllocCommand( 460 IN COMMAND_TYPE Type, 461 IN PCTSTR CmdHead OPTIONAL, 462 IN PCTSTR CmdTail OPTIONAL) 463 { 464 PARSED_COMMAND* Cmd; 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 474 Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, 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 527 FreeCommand( 528 IN OUT PARSED_COMMAND* Cmd) 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 551 ParseRedirection( 552 IN OUT REDIRECTION** List) 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 { 563 ASSERT(CurrentTokenType != TOK_REDIRECTION); 564 return FALSE; 565 } 566 ASSERT((CurrentTokenType == TOK_REDIRECTION) || 567 (CurrentTokenType == TOK_NORMAL)); 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 { 605 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 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; 629 Redir->OldHandle = INVALID_HANDLE_VALUE; 630 Redir->Number = Number; 631 Redir->Mode = RedirMode; 632 _tcscpy(Redir->Filename, Tok); 633 *List = Redir; 634 return TRUE; 635 636 fail: 637 ParseError(); 638 FreeRedirection(*List); 639 *List = NULL; 640 return FALSE; 641 } 642 643 static __inline PARSED_COMMAND* 644 ParseCommandOp( 645 IN COMMAND_TYPE OpType); 646 647 /* Parse a parenthesized block */ 648 static PARSED_COMMAND* 649 ParseBlock( 650 IN OUT REDIRECTION** RedirList) 651 { 652 PARSED_COMMAND *Cmd, *Sub, **NextPtr; 653 654 Cmd = AllocCommand(C_BLOCK, NULL, NULL); 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 { 687 ASSERT(CurrentTokenType == TOK_END); 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 697 Sub = ParseCommandOp(C_OP_LOWEST); 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 710 if (CurrentTokenType == TOK_END_BLOCK) 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 728 while (ParseToken(0, STANDARD_SEPS) == TOK_REDIRECTION) 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 */ 772 ParseToken(0, STANDARD_SEPS); 773 } 774 775 return Cmd; 776 } 777 778 /* Parse an IF statement */ 779 static PARSED_COMMAND* 780 ParseIf(VOID) 781 { 782 PARSED_COMMAND* Cmd; 783 784 Cmd = AllocCommand(C_IF, NULL, NULL); 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; 795 ParseToken(0, STANDARD_SEPS); 796 } 797 if (_tcsicmp(CurrentToken, _T("not")) == 0) 798 { 799 Cmd->If.Flags |= IFFLAG_NEGATE; 800 ParseToken(0, STANDARD_SEPS); 801 } 802 803 if (CurrentTokenType != TOK_NORMAL) 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 { 815 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 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 { 843 if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL) 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* 875 ParseFor(VOID) 876 { 877 PARSED_COMMAND* Cmd; 878 879 /* Use the scratch buffer */ 880 PTSTR List = TempBuf; 881 PTCHAR Pos = List; 882 883 Cmd = AllocCommand(C_FOR, NULL, NULL); 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 { 906 ParseToken(0, STANDARD_SEPS); 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 { 921 ParseToken(0, STANDARD_SEPS); 922 if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%')) 923 break; 924 StripQuotes(CurrentToken); 925 Cmd->For.Params = cmd_dup(CurrentToken); 926 } 927 } 928 else 929 { 930 break; 931 } 932 933 ParseToken(0, STANDARD_SEPS); 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 951 ParseToken(0, STANDARD_SEPS); 952 if (_tcsicmp(CurrentToken, _T("in")) != 0) 953 goto error; 954 955 if (ParseToken(_T('('), STANDARD_SEPS) != TOK_BEGIN_BLOCK) 956 goto error; 957 958 while (TRUE) 959 { 960 /* Pretend we're inside a block so the tokenizer will stop on ')' */ 961 ++InsideBlock; 962 ParseToken(0, STANDARD_SEPS); 963 --InsideBlock; 964 965 if (CurrentTokenType == TOK_END_BLOCK) 966 break; 967 968 /* Skip past the \n */ 969 if ((CurrentTokenType == TOK_END) && *CurrentToken == _T('\n')) 970 continue; 971 972 if (CurrentTokenType != TOK_NORMAL) 973 goto error; 974 975 if (Pos != List) 976 *Pos++ = _T(' '); 977 978 if (Pos + _tcslen(CurrentToken) >= &List[CMDLINE_LENGTH]) 979 goto error; 980 Pos = _stpcpy(Pos, CurrentToken); 981 } 982 *Pos = _T('\0'); 983 Cmd->For.List = cmd_dup(List); 984 985 ParseToken(0, STANDARD_SEPS); 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* 1003 ParseRem(VOID) 1004 { 1005 PARSED_COMMAND* Cmd; 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 } 1033 Pos = _stpcpy(Pos, CurrentToken); 1034 } 1035 *Pos = _T('\0'); 1036 1037 Cmd = AllocCommand(C_REM, 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* 1051 ParseCommandPart( 1052 IN OUT REDIRECTION** RedirList) 1053 { 1054 PARSED_COMMAND* Cmd; 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 1071 ParseToken(0, STANDARD_SEPS); 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 */ 1077 ParseToken(0, STANDARD_SEPS); 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 { 1121 if (CurrentTokenType == TOK_NORMAL) 1122 { 1123 if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH]) 1124 { 1125 ParseError(); 1126 return NULL; 1127 } 1128 Pos = _stpcpy(Pos, CurrentToken); 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 */ 1134 while (CurrentTokenType == TOK_REDIRECTION) 1135 { 1136 if (!ParseRedirection(RedirList)) 1137 return NULL; 1138 1139 ParseToken(0, STANDARD_SEPS); 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 1158 ASSERT(CurrentTokenType != TOK_END); 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 1174 ParseToken(0, STANDARD_SEPS); 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 1196 Cmd = AllocCommand(C_COMMAND, 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* 1209 ParsePrimary(VOID) 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 { 1217 Cmd = AllocCommand(C_QUIET, NULL, NULL); 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 1232 while (CurrentTokenType == TOK_REDIRECTION) 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 1256 ParseToken(0, STANDARD_SEPS); 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 1269 if (CurrentTokenType == TOK_NORMAL) 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* 1295 ParseCommandBinaryOp( 1296 IN COMMAND_TYPE OpType) 1297 { 1298 PARSED_COMMAND* Cmd; 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 (/** bc && **/ (_tcscmp(CurrentToken, _T(")")) == 0) && 1372 (CurrentTokenType != TOK_END_BLOCK)) 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 { 1390 ASSERT(CurrentTokenType == TOK_END); 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* 1447 ParseCommandOp( 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'. */ 1454 ParseTokenEx(_T('@'), _T('('), STANDARD_SEPS, bHandleContinuations); 1455 1456 return ParseCommandBinaryOp(OpType); 1457 } 1458 1459 1460 PARSED_COMMAND* 1461 ParseCommand( 1462 IN PCTSTR Line) 1463 { 1464 PARSED_COMMAND* Cmd; 1465 1466 if (Line) 1467 { 1468 if (!SubstituteVars(Line, ParseLine, _T('%'))) 1469 return NULL; 1470 bLineContinuations = FALSE; 1471 } 1472 else 1473 { 1474 if (!ReadLine(ParseLine, FALSE)) 1475 return NULL; 1476 bLineContinuations = TRUE; 1477 } 1478 1479 InitParser(); 1480 1481 Cmd = ParseCommandOp(C_OP_LOWEST); 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 1514 DumpRedir( 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 1533 DumpCommand( 1534 IN PARSED_COMMAND* Cmd, 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 1808 EchoCommand( 1809 IN PARSED_COMMAND* Cmd) 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 1965 UnparseCommand( 1966 IN PARSED_COMMAND* Cmd, 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 } 2108