mirror of
https://github.com/reactos/reactos.git
synced 2024-10-30 11:35:58 +00:00
3f892a8d6b
Adapted from a patch by Jacob S. Preciado. Bring also the code suggestions emitted during review.
1034 lines
26 KiB
C
1034 lines
26 KiB
C
/*
|
|
* PARSER.C - command parsing.
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#define C_OP_LOWEST C_MULTI
|
|
#define C_OP_HIGHEST C_PIPE
|
|
static const TCHAR OpString[][3] = { _T("&"), _T("||"), _T("&&"), _T("|") };
|
|
|
|
static const TCHAR RedirString[][3] = { _T("<"), _T(">"), _T(">>") };
|
|
|
|
static const TCHAR *const IfOperatorString[] =
|
|
{
|
|
_T("cmdextversion"),
|
|
_T("defined"),
|
|
_T("errorlevel"),
|
|
_T("exist"),
|
|
#define IF_MAX_UNARY IF_EXIST
|
|
_T("=="),
|
|
_T("equ"),
|
|
_T("gtr"),
|
|
_T("geq"),
|
|
_T("lss"),
|
|
_T("leq"),
|
|
_T("neq"),
|
|
#define IF_MAX_COMPARISON IF_NEQ
|
|
};
|
|
|
|
/* These three characters act like spaces to the parser in most contexts */
|
|
#define STANDARD_SEPS _T(",;=")
|
|
|
|
static BOOL IsSeparator(TCHAR Char)
|
|
{
|
|
return _istspace(Char) || (Char && _tcschr(STANDARD_SEPS, Char));
|
|
}
|
|
|
|
enum
|
|
{
|
|
TOK_END,
|
|
TOK_NORMAL,
|
|
TOK_OPERATOR,
|
|
TOK_REDIRECTION,
|
|
TOK_BEGIN_BLOCK,
|
|
TOK_END_BLOCK
|
|
};
|
|
|
|
static BOOL bParseError;
|
|
static BOOL bLineContinuations;
|
|
static TCHAR ParseLine[CMDLINE_LENGTH];
|
|
static TCHAR *ParsePos;
|
|
static TCHAR CurChar;
|
|
|
|
static TCHAR CurrentToken[CMDLINE_LENGTH];
|
|
static int CurrentTokenType;
|
|
static int InsideBlock;
|
|
|
|
static TCHAR ParseChar(void)
|
|
{
|
|
TCHAR Char;
|
|
|
|
if (bParseError)
|
|
return (CurChar = 0);
|
|
|
|
restart:
|
|
/*
|
|
* Although CRs can be injected into a line via an environment
|
|
* variable substitution, the parser ignores them - they won't
|
|
* even separate tokens.
|
|
*/
|
|
do
|
|
{
|
|
Char = *ParsePos++;
|
|
}
|
|
while (Char == _T('\r'));
|
|
|
|
if (!Char)
|
|
{
|
|
ParsePos--;
|
|
if (bLineContinuations)
|
|
{
|
|
if (!ReadLine(ParseLine, TRUE))
|
|
{
|
|
/* ^C pressed, or line was too long */
|
|
bParseError = TRUE;
|
|
}
|
|
else if (*(ParsePos = ParseLine))
|
|
{
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
return (CurChar = Char);
|
|
}
|
|
|
|
static void ParseError(void)
|
|
{
|
|
error_syntax(CurrentTokenType != TOK_END ? CurrentToken : NULL);
|
|
bParseError = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Yes, cmd has a Lexical Analyzer. Whenever the parser gives an "xxx was
|
|
* unexpected at this time." message, it shows what the last token read was.
|
|
*/
|
|
static int ParseToken(TCHAR ExtraEnd, TCHAR *Separators)
|
|
{
|
|
TCHAR *Out = CurrentToken;
|
|
TCHAR Char;
|
|
int Type;
|
|
BOOL bInQuote = FALSE;
|
|
|
|
for (Char = CurChar; Char && Char != _T('\n'); Char = ParseChar())
|
|
{
|
|
bInQuote ^= (Char == _T('"'));
|
|
if (!bInQuote)
|
|
{
|
|
if (Separators != NULL)
|
|
{
|
|
if (_istspace(Char) || _tcschr(Separators, Char))
|
|
{
|
|
/* Skip leading separators */
|
|
if (Out == CurrentToken)
|
|
continue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check for numbered redirection */
|
|
if ((Char >= _T('0') && Char <= _T('9') &&
|
|
(ParsePos == &ParseLine[1] || IsSeparator(ParsePos[-2]))
|
|
&& (*ParsePos == _T('<') || *ParsePos == _T('>'))))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (Char == ExtraEnd)
|
|
break;
|
|
if (InsideBlock && Char == _T(')'))
|
|
break;
|
|
if (_tcschr(_T("&|<>"), Char))
|
|
break;
|
|
|
|
if (Char == _T('^'))
|
|
{
|
|
Char = ParseChar();
|
|
/* Eat up a \n, allowing line continuation */
|
|
if (Char == _T('\n'))
|
|
Char = ParseChar();
|
|
/* Next character is a forced literal */
|
|
}
|
|
}
|
|
if (Out == &CurrentToken[CMDLINE_LENGTH - 1])
|
|
break;
|
|
*Out++ = Char;
|
|
}
|
|
|
|
/* Check if we got at least one character before reaching a special one.
|
|
* If so, return them and leave the special for the next call. */
|
|
if (Out != CurrentToken)
|
|
{
|
|
Type = TOK_NORMAL;
|
|
}
|
|
else if (Char == _T('('))
|
|
{
|
|
Type = TOK_BEGIN_BLOCK;
|
|
*Out++ = Char;
|
|
ParseChar();
|
|
}
|
|
else if (Char == _T(')'))
|
|
{
|
|
Type = TOK_END_BLOCK;
|
|
*Out++ = Char;
|
|
ParseChar();
|
|
}
|
|
else if (Char == _T('&') || Char == _T('|'))
|
|
{
|
|
Type = TOK_OPERATOR;
|
|
*Out++ = Char;
|
|
Char = ParseChar();
|
|
/* check for && or || */
|
|
if (Char == Out[-1])
|
|
{
|
|
*Out++ = Char;
|
|
ParseChar();
|
|
}
|
|
}
|
|
else if ((Char >= _T('0') && Char <= _T('9'))
|
|
|| (Char == _T('<') || Char == _T('>')))
|
|
{
|
|
Type = TOK_REDIRECTION;
|
|
if (Char >= _T('0') && Char <= _T('9'))
|
|
{
|
|
*Out++ = Char;
|
|
Char = ParseChar();
|
|
}
|
|
*Out++ = Char;
|
|
Char = ParseChar();
|
|
if (Char == Out[-1])
|
|
{
|
|
/* Strangely, the tokenizer allows << as well as >>... (it
|
|
* will cause an error when trying to parse it though) */
|
|
*Out++ = Char;
|
|
Char = ParseChar();
|
|
}
|
|
if (Char == _T('&'))
|
|
{
|
|
*Out++ = Char;
|
|
while (IsSeparator(Char = ParseChar()))
|
|
;
|
|
if (Char >= _T('0') && Char <= _T('9'))
|
|
{
|
|
*Out++ = Char;
|
|
ParseChar();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Type = TOK_END;
|
|
}
|
|
*Out = _T('\0');
|
|
return CurrentTokenType = Type;
|
|
}
|
|
|
|
static BOOL ParseRedirection(REDIRECTION **List)
|
|
{
|
|
TCHAR *Tok = CurrentToken;
|
|
BYTE Number;
|
|
REDIR_MODE RedirMode;
|
|
REDIRECTION *Redir;
|
|
|
|
if (*Tok >= _T('0') && *Tok <= _T('9'))
|
|
Number = *Tok++ - _T('0');
|
|
else
|
|
Number = *Tok == _T('<') ? 0 : 1;
|
|
|
|
if (*Tok++ == _T('<'))
|
|
{
|
|
RedirMode = REDIR_READ;
|
|
if (*Tok == _T('<'))
|
|
goto fail;
|
|
}
|
|
else
|
|
{
|
|
RedirMode = REDIR_WRITE;
|
|
if (*Tok == _T('>'))
|
|
{
|
|
RedirMode = REDIR_APPEND;
|
|
Tok++;
|
|
}
|
|
}
|
|
|
|
if (!*Tok)
|
|
{
|
|
/* The file name was not part of this token, so it'll be the next one */
|
|
if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
|
|
goto fail;
|
|
Tok = CurrentToken;
|
|
}
|
|
|
|
/* If a redirection for this handle number already exists, delete it */
|
|
while ((Redir = *List))
|
|
{
|
|
if (Redir->Number == Number)
|
|
{
|
|
*List = Redir->Next;
|
|
cmd_free(Redir);
|
|
continue;
|
|
}
|
|
List = &Redir->Next;
|
|
}
|
|
|
|
Redir = cmd_alloc(FIELD_OFFSET(REDIRECTION, Filename[_tcslen(Tok) + 1]));
|
|
if (!Redir)
|
|
{
|
|
WARN("Cannot allocate memory for Redir!\n");
|
|
goto fail;
|
|
}
|
|
Redir->Next = NULL;
|
|
Redir->OldHandle = INVALID_HANDLE_VALUE;
|
|
Redir->Number = Number;
|
|
Redir->Mode = RedirMode;
|
|
_tcscpy(Redir->Filename, Tok);
|
|
*List = Redir;
|
|
return TRUE;
|
|
|
|
fail:
|
|
ParseError();
|
|
FreeRedirection(*List);
|
|
*List = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
static PARSED_COMMAND *ParseCommandOp(int OpType);
|
|
|
|
/* Parse a parenthesized block */
|
|
static PARSED_COMMAND *ParseBlock(REDIRECTION *RedirList)
|
|
{
|
|
PARSED_COMMAND *Cmd, *Sub, **NextPtr;
|
|
|
|
Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
|
|
if (!Cmd)
|
|
{
|
|
WARN("Cannot allocate memory for Cmd!\n");
|
|
ParseError();
|
|
FreeRedirection(RedirList);
|
|
return NULL;
|
|
}
|
|
Cmd->Type = C_BLOCK;
|
|
Cmd->Next = NULL;
|
|
Cmd->Subcommands = NULL;
|
|
Cmd->Redirections = RedirList;
|
|
|
|
/* Read the block contents */
|
|
NextPtr = &Cmd->Subcommands;
|
|
InsideBlock++;
|
|
while (1)
|
|
{
|
|
Sub = ParseCommandOp(C_OP_LOWEST);
|
|
if (Sub)
|
|
{
|
|
*NextPtr = Sub;
|
|
NextPtr = &Sub->Next;
|
|
}
|
|
else if (bParseError)
|
|
{
|
|
InsideBlock--;
|
|
FreeCommand(Cmd);
|
|
return NULL;
|
|
}
|
|
|
|
if (CurrentTokenType == TOK_END_BLOCK)
|
|
break;
|
|
|
|
/* Skip past the \n */
|
|
ParseChar();
|
|
}
|
|
InsideBlock--;
|
|
|
|
/* Process any trailing redirections */
|
|
while (ParseToken(0, STANDARD_SEPS) == TOK_REDIRECTION)
|
|
{
|
|
if (!ParseRedirection(&Cmd->Redirections))
|
|
{
|
|
FreeCommand(Cmd);
|
|
return NULL;
|
|
}
|
|
}
|
|
return Cmd;
|
|
}
|
|
|
|
/* Parse an IF statement */
|
|
static PARSED_COMMAND *ParseIf(void)
|
|
{
|
|
int Type;
|
|
PARSED_COMMAND *Cmd;
|
|
|
|
Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
|
|
if (!Cmd)
|
|
{
|
|
WARN("Cannot allocate memory for Cmd!\n");
|
|
ParseError();
|
|
return NULL;
|
|
}
|
|
memset(Cmd, 0, sizeof(PARSED_COMMAND));
|
|
Cmd->Type = C_IF;
|
|
|
|
Type = CurrentTokenType;
|
|
if (_tcsicmp(CurrentToken, _T("/I")) == 0)
|
|
{
|
|
Cmd->If.Flags |= IFFLAG_IGNORECASE;
|
|
Type = ParseToken(0, STANDARD_SEPS);
|
|
}
|
|
if (_tcsicmp(CurrentToken, _T("not")) == 0)
|
|
{
|
|
Cmd->If.Flags |= IFFLAG_NEGATE;
|
|
Type = ParseToken(0, STANDARD_SEPS);
|
|
}
|
|
|
|
if (Type != TOK_NORMAL)
|
|
{
|
|
FreeCommand(Cmd);
|
|
ParseError();
|
|
return NULL;
|
|
}
|
|
|
|
/* Check for unary operators */
|
|
for (; Cmd->If.Operator <= IF_MAX_UNARY; Cmd->If.Operator++)
|
|
{
|
|
if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
|
|
{
|
|
if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
|
|
{
|
|
FreeCommand(Cmd);
|
|
ParseError();
|
|
return NULL;
|
|
}
|
|
Cmd->If.RightArg = cmd_dup(CurrentToken);
|
|
goto condition_done;
|
|
}
|
|
}
|
|
|
|
/* It must be a two-argument (comparison) operator. It could be ==, so
|
|
* the equals sign can't be treated as whitespace here. */
|
|
Cmd->If.LeftArg = cmd_dup(CurrentToken);
|
|
ParseToken(0, _T(",;"));
|
|
|
|
/* The right argument can come immediately after == */
|
|
if (_tcsnicmp(CurrentToken, _T("=="), 2) == 0 && CurrentToken[2])
|
|
{
|
|
Cmd->If.RightArg = cmd_dup(&CurrentToken[2]);
|
|
goto condition_done;
|
|
}
|
|
|
|
for (; Cmd->If.Operator <= IF_MAX_COMPARISON; Cmd->If.Operator++)
|
|
{
|
|
if (_tcsicmp(CurrentToken, IfOperatorString[Cmd->If.Operator]) == 0)
|
|
{
|
|
if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
|
|
break;
|
|
Cmd->If.RightArg = cmd_dup(CurrentToken);
|
|
goto condition_done;
|
|
}
|
|
}
|
|
FreeCommand(Cmd);
|
|
ParseError();
|
|
return NULL;
|
|
|
|
condition_done:
|
|
Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
|
|
if (Cmd->Subcommands == NULL)
|
|
{
|
|
FreeCommand(Cmd);
|
|
return NULL;
|
|
}
|
|
if (_tcsicmp(CurrentToken, _T("else")) == 0)
|
|
{
|
|
Cmd->Subcommands->Next = ParseCommandOp(C_OP_LOWEST);
|
|
if (Cmd->Subcommands->Next == NULL)
|
|
{
|
|
FreeCommand(Cmd);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return Cmd;
|
|
}
|
|
|
|
/*
|
|
* Parse a FOR command.
|
|
* Syntax is: FOR [options] %var IN (list) DO command
|
|
*/
|
|
static PARSED_COMMAND *ParseFor(void)
|
|
{
|
|
PARSED_COMMAND *Cmd;
|
|
TCHAR List[CMDLINE_LENGTH];
|
|
TCHAR *Pos = List;
|
|
|
|
Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
|
|
if (!Cmd)
|
|
{
|
|
WARN("Cannot allocate memory for Cmd!\n");
|
|
ParseError();
|
|
return NULL;
|
|
}
|
|
memset(Cmd, 0, sizeof(PARSED_COMMAND));
|
|
Cmd->Type = C_FOR;
|
|
|
|
while (1)
|
|
{
|
|
if (_tcsicmp(CurrentToken, _T("/D")) == 0)
|
|
Cmd->For.Switches |= FOR_DIRS;
|
|
else if (_tcsicmp(CurrentToken, _T("/F")) == 0)
|
|
{
|
|
Cmd->For.Switches |= FOR_F;
|
|
if (!Cmd->For.Params)
|
|
{
|
|
ParseToken(0, STANDARD_SEPS);
|
|
if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
|
|
break;
|
|
Cmd->For.Params = cmd_dup(CurrentToken);
|
|
}
|
|
}
|
|
else if (_tcsicmp(CurrentToken, _T("/L")) == 0)
|
|
Cmd->For.Switches |= FOR_LOOP;
|
|
else if (_tcsicmp(CurrentToken, _T("/R")) == 0)
|
|
{
|
|
Cmd->For.Switches |= FOR_RECURSIVE;
|
|
if (!Cmd->For.Params)
|
|
{
|
|
ParseToken(0, STANDARD_SEPS);
|
|
if (CurrentToken[0] == _T('/') || CurrentToken[0] == _T('%'))
|
|
break;
|
|
StripQuotes(CurrentToken);
|
|
Cmd->For.Params = cmd_dup(CurrentToken);
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
ParseToken(0, STANDARD_SEPS);
|
|
}
|
|
|
|
/* Make sure there aren't two different switches specified
|
|
* at the same time, unless they're /D and /R */
|
|
if ((Cmd->For.Switches & (Cmd->For.Switches - 1)) != 0
|
|
&& Cmd->For.Switches != (FOR_DIRS | FOR_RECURSIVE))
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
/* Variable name should be % and just one other character */
|
|
if (CurrentToken[0] != _T('%') || _tcslen(CurrentToken) != 2)
|
|
goto error;
|
|
Cmd->For.Variable = CurrentToken[1];
|
|
|
|
ParseToken(0, STANDARD_SEPS);
|
|
if (_tcsicmp(CurrentToken, _T("in")) != 0)
|
|
goto error;
|
|
|
|
if (ParseToken(_T('('), STANDARD_SEPS) != TOK_BEGIN_BLOCK)
|
|
goto error;
|
|
|
|
while (1)
|
|
{
|
|
int Type;
|
|
|
|
/* Pretend we're inside a block so the tokenizer will stop on ')' */
|
|
InsideBlock++;
|
|
Type = ParseToken(0, STANDARD_SEPS);
|
|
InsideBlock--;
|
|
|
|
if (Type == TOK_END_BLOCK)
|
|
break;
|
|
|
|
if (Type == TOK_END)
|
|
{
|
|
/* Skip past the \n */
|
|
ParseChar();
|
|
continue;
|
|
}
|
|
|
|
if (Type != TOK_NORMAL)
|
|
goto error;
|
|
|
|
if (Pos != List)
|
|
*Pos++ = _T(' ');
|
|
|
|
if (Pos + _tcslen(CurrentToken) >= &List[CMDLINE_LENGTH])
|
|
goto error;
|
|
Pos = _stpcpy(Pos, CurrentToken);
|
|
}
|
|
*Pos = _T('\0');
|
|
Cmd->For.List = cmd_dup(List);
|
|
|
|
ParseToken(0, STANDARD_SEPS);
|
|
if (_tcsicmp(CurrentToken, _T("do")) != 0)
|
|
goto error;
|
|
|
|
Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
|
|
if (Cmd->Subcommands == NULL)
|
|
{
|
|
FreeCommand(Cmd);
|
|
return NULL;
|
|
}
|
|
|
|
return Cmd;
|
|
|
|
error:
|
|
FreeCommand(Cmd);
|
|
ParseError();
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse a REM command */
|
|
static PARSED_COMMAND *ParseRem(void)
|
|
{
|
|
/* Just ignore the rest of the line */
|
|
while (CurChar && CurChar != _T('\n'))
|
|
ParseChar();
|
|
return NULL;
|
|
}
|
|
|
|
static DECLSPEC_NOINLINE PARSED_COMMAND *ParseCommandPart(REDIRECTION *RedirList)
|
|
{
|
|
TCHAR ParsedLine[CMDLINE_LENGTH];
|
|
PARSED_COMMAND *Cmd;
|
|
PARSED_COMMAND *(*Func)(void);
|
|
|
|
TCHAR *Pos = _stpcpy(ParsedLine, CurrentToken) + 1;
|
|
DWORD_PTR TailOffset = Pos - ParsedLine;
|
|
|
|
/* Check for special forms */
|
|
if ((Func = ParseFor, _tcsicmp(ParsedLine, _T("for")) == 0) ||
|
|
(Func = ParseIf, _tcsicmp(ParsedLine, _T("if")) == 0) ||
|
|
(Func = ParseRem, _tcsicmp(ParsedLine, _T("rem")) == 0))
|
|
{
|
|
ParseToken(0, STANDARD_SEPS);
|
|
/* Do special parsing only if it's not followed by /? */
|
|
if (_tcscmp(CurrentToken, _T("/?")) != 0)
|
|
{
|
|
if (RedirList)
|
|
{
|
|
ParseError();
|
|
FreeRedirection(RedirList);
|
|
return NULL;
|
|
}
|
|
return Func();
|
|
}
|
|
Pos = _stpcpy(Pos, _T(" /?"));
|
|
}
|
|
|
|
/* Now get the tail */
|
|
while (1)
|
|
{
|
|
int Type = ParseToken(0, NULL);
|
|
if (Type == TOK_NORMAL)
|
|
{
|
|
if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
|
|
{
|
|
ParseError();
|
|
FreeRedirection(RedirList);
|
|
return NULL;
|
|
}
|
|
Pos = _stpcpy(Pos, CurrentToken);
|
|
}
|
|
else if (Type == TOK_REDIRECTION)
|
|
{
|
|
if (!ParseRedirection(&RedirList))
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
*Pos++ = _T('\0');
|
|
|
|
Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.First[Pos - ParsedLine]));
|
|
if (!Cmd)
|
|
{
|
|
WARN("Cannot allocate memory for Cmd!\n");
|
|
ParseError();
|
|
FreeRedirection(RedirList);
|
|
return NULL;
|
|
}
|
|
Cmd->Type = C_COMMAND;
|
|
Cmd->Next = NULL;
|
|
Cmd->Subcommands = NULL;
|
|
Cmd->Redirections = RedirList;
|
|
memcpy(Cmd->Command.First, ParsedLine, (Pos - ParsedLine) * sizeof(TCHAR));
|
|
Cmd->Command.Rest = Cmd->Command.First + TailOffset;
|
|
return Cmd;
|
|
}
|
|
|
|
static PARSED_COMMAND *ParsePrimary(void)
|
|
{
|
|
REDIRECTION *RedirList = NULL;
|
|
int Type;
|
|
|
|
while (IsSeparator(CurChar))
|
|
{
|
|
if (CurChar == _T('\n'))
|
|
return NULL;
|
|
ParseChar();
|
|
}
|
|
|
|
if (!CurChar)
|
|
return NULL;
|
|
|
|
if (CurChar == _T(':'))
|
|
{
|
|
/* "Ignore" the rest of the line.
|
|
* (Line continuations will still be parsed, though.) */
|
|
while (ParseToken(0, NULL) != TOK_END)
|
|
;
|
|
return NULL;
|
|
}
|
|
|
|
if (CurChar == _T('@'))
|
|
{
|
|
PARSED_COMMAND *Cmd;
|
|
ParseChar();
|
|
Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
|
|
if (!Cmd)
|
|
{
|
|
WARN("Cannot allocate memory for Cmd!\n");
|
|
ParseError();
|
|
return NULL;
|
|
}
|
|
Cmd->Type = C_QUIET;
|
|
Cmd->Next = NULL;
|
|
/* @ acts like a unary operator with low precedence,
|
|
* so call the top-level parser */
|
|
Cmd->Subcommands = ParseCommandOp(C_OP_LOWEST);
|
|
Cmd->Redirections = NULL;
|
|
return Cmd;
|
|
}
|
|
|
|
/* Process leading redirections and get the head of the command */
|
|
while ((Type = ParseToken(_T('('), STANDARD_SEPS)) == TOK_REDIRECTION)
|
|
{
|
|
if (!ParseRedirection(&RedirList))
|
|
return NULL;
|
|
}
|
|
|
|
if (Type == TOK_NORMAL)
|
|
return ParseCommandPart(RedirList);
|
|
else if (Type == TOK_BEGIN_BLOCK)
|
|
return ParseBlock(RedirList);
|
|
else if (Type == TOK_END_BLOCK && !RedirList)
|
|
return NULL;
|
|
|
|
ParseError();
|
|
FreeRedirection(RedirList);
|
|
return NULL;
|
|
}
|
|
|
|
static PARSED_COMMAND *ParseCommandOp(int OpType)
|
|
{
|
|
PARSED_COMMAND *Cmd, *Left, *Right;
|
|
|
|
if (OpType == C_OP_HIGHEST)
|
|
Cmd = ParsePrimary();
|
|
else
|
|
Cmd = ParseCommandOp(OpType + 1);
|
|
|
|
if (Cmd && !_tcscmp(CurrentToken, OpString[OpType - C_OP_LOWEST]))
|
|
{
|
|
Left = Cmd;
|
|
Right = ParseCommandOp(OpType);
|
|
if (!Right)
|
|
{
|
|
if (!bParseError)
|
|
{
|
|
/* & is allowed to have an empty RHS */
|
|
if (OpType == C_MULTI)
|
|
return Left;
|
|
ParseError();
|
|
}
|
|
FreeCommand(Left);
|
|
return NULL;
|
|
}
|
|
|
|
Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
|
|
if (!Cmd)
|
|
{
|
|
WARN("Cannot allocate memory for Cmd!\n");
|
|
ParseError();
|
|
FreeCommand(Left);
|
|
FreeCommand(Right);
|
|
return NULL;
|
|
}
|
|
Cmd->Type = OpType;
|
|
Cmd->Next = NULL;
|
|
Cmd->Redirections = NULL;
|
|
Cmd->Subcommands = Left;
|
|
Left->Next = Right;
|
|
Right->Next = NULL;
|
|
}
|
|
|
|
return Cmd;
|
|
}
|
|
|
|
PARSED_COMMAND *
|
|
ParseCommand(LPTSTR Line)
|
|
{
|
|
PARSED_COMMAND *Cmd;
|
|
|
|
if (Line)
|
|
{
|
|
if (!SubstituteVars(Line, ParseLine, _T('%')))
|
|
return NULL;
|
|
bLineContinuations = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (!ReadLine(ParseLine, FALSE))
|
|
return NULL;
|
|
bLineContinuations = TRUE;
|
|
}
|
|
bParseError = FALSE;
|
|
ParsePos = ParseLine;
|
|
CurChar = _T(' ');
|
|
|
|
Cmd = ParseCommandOp(C_OP_LOWEST);
|
|
if (Cmd)
|
|
{
|
|
if (CurrentTokenType != TOK_END)
|
|
ParseError();
|
|
if (bParseError)
|
|
{
|
|
FreeCommand(Cmd);
|
|
Cmd = NULL;
|
|
}
|
|
bIgnoreEcho = FALSE;
|
|
}
|
|
else
|
|
{
|
|
bIgnoreEcho = TRUE;
|
|
}
|
|
return Cmd;
|
|
}
|
|
|
|
|
|
/*
|
|
* Reconstruct a parse tree into text form; used for echoing
|
|
* batch file commands and FOR instances.
|
|
*/
|
|
VOID
|
|
EchoCommand(PARSED_COMMAND *Cmd)
|
|
{
|
|
TCHAR Buf[CMDLINE_LENGTH];
|
|
PARSED_COMMAND *Sub;
|
|
REDIRECTION *Redir;
|
|
|
|
switch (Cmd->Type)
|
|
{
|
|
case C_COMMAND:
|
|
if (SubstituteForVars(Cmd->Command.First, Buf))
|
|
ConOutPrintf(_T("%s"), Buf);
|
|
if (SubstituteForVars(Cmd->Command.Rest, Buf))
|
|
ConOutPrintf(_T("%s"), Buf);
|
|
break;
|
|
case C_QUIET:
|
|
return;
|
|
case C_BLOCK:
|
|
ConOutChar(_T('('));
|
|
Sub = Cmd->Subcommands;
|
|
if (Sub && !Sub->Next)
|
|
{
|
|
/* Single-command block: display all on one line */
|
|
EchoCommand(Sub);
|
|
}
|
|
else if (Sub)
|
|
{
|
|
/* Multi-command block: display parenthesis on separate lines */
|
|
ConOutChar(_T('\n'));
|
|
do
|
|
{
|
|
EchoCommand(Sub);
|
|
ConOutChar(_T('\n'));
|
|
Sub = Sub->Next;
|
|
} while (Sub);
|
|
}
|
|
ConOutChar(_T(')'));
|
|
break;
|
|
case C_MULTI:
|
|
case C_IFFAILURE:
|
|
case C_IFSUCCESS:
|
|
case C_PIPE:
|
|
Sub = Cmd->Subcommands;
|
|
EchoCommand(Sub);
|
|
ConOutPrintf(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
|
|
EchoCommand(Sub->Next);
|
|
break;
|
|
case C_IF:
|
|
ConOutPrintf(_T("if"));
|
|
if (Cmd->If.Flags & IFFLAG_IGNORECASE)
|
|
ConOutPrintf(_T(" /I"));
|
|
if (Cmd->If.Flags & IFFLAG_NEGATE)
|
|
ConOutPrintf(_T(" not"));
|
|
if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf))
|
|
ConOutPrintf(_T(" %s"), Buf);
|
|
ConOutPrintf(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
|
|
if (SubstituteForVars(Cmd->If.RightArg, Buf))
|
|
ConOutPrintf(_T(" %s "), Buf);
|
|
Sub = Cmd->Subcommands;
|
|
EchoCommand(Sub);
|
|
if (Sub->Next)
|
|
{
|
|
ConOutPrintf(_T(" else "));
|
|
EchoCommand(Sub->Next);
|
|
}
|
|
break;
|
|
case C_FOR:
|
|
ConOutPrintf(_T("for"));
|
|
if (Cmd->For.Switches & FOR_DIRS) ConOutPrintf(_T(" /D"));
|
|
if (Cmd->For.Switches & FOR_F) ConOutPrintf(_T(" /F"));
|
|
if (Cmd->For.Switches & FOR_LOOP) ConOutPrintf(_T(" /L"));
|
|
if (Cmd->For.Switches & FOR_RECURSIVE) ConOutPrintf(_T(" /R"));
|
|
if (Cmd->For.Params)
|
|
ConOutPrintf(_T(" %s"), Cmd->For.Params);
|
|
ConOutPrintf(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
|
|
EchoCommand(Cmd->Subcommands);
|
|
break;
|
|
}
|
|
|
|
for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
|
|
{
|
|
if (SubstituteForVars(Redir->Filename, Buf))
|
|
{
|
|
ConOutPrintf(_T(" %c%s%s"), _T('0') + Redir->Number,
|
|
RedirString[Redir->Mode], Buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "Unparse" a command into a text form suitable for passing to CMD /C.
|
|
* Used for pipes. This is basically the same thing as EchoCommand, but
|
|
* writing into a string instead of to standard output.
|
|
*/
|
|
TCHAR *
|
|
Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd)
|
|
{
|
|
TCHAR Buf[CMDLINE_LENGTH];
|
|
PARSED_COMMAND *Sub;
|
|
REDIRECTION *Redir;
|
|
|
|
/*
|
|
* Since this function has the annoying requirement that it must avoid
|
|
* overflowing the supplied buffer, define some helper macros to make
|
|
* this less painful.
|
|
*/
|
|
#define CHAR(Char) \
|
|
do { \
|
|
if (Out == OutEnd) return NULL; \
|
|
*Out++ = Char; \
|
|
} while (0)
|
|
#define STRING(String) \
|
|
do { \
|
|
if (Out + _tcslen(String) > OutEnd) return NULL; \
|
|
Out = _stpcpy(Out, String); \
|
|
} while (0)
|
|
#define PRINTF(Format, ...) \
|
|
do { \
|
|
UINT Len = _sntprintf(Out, OutEnd - Out, Format, __VA_ARGS__); \
|
|
if (Len > (UINT)(OutEnd - Out)) return NULL; \
|
|
Out += Len; \
|
|
} while (0)
|
|
#define RECURSE(Subcommand) \
|
|
do { \
|
|
Out = Unparse(Subcommand, Out, OutEnd); \
|
|
if (!Out) return NULL; \
|
|
} while (0)
|
|
|
|
switch (Cmd->Type)
|
|
{
|
|
case C_COMMAND:
|
|
/* This is fragile since there could be special characters, but
|
|
* Windows doesn't bother escaping them, so for compatibility
|
|
* we probably shouldn't do it either */
|
|
if (!SubstituteForVars(Cmd->Command.First, Buf)) return NULL;
|
|
STRING(Buf);
|
|
if (!SubstituteForVars(Cmd->Command.Rest, Buf)) return NULL;
|
|
STRING(Buf);
|
|
break;
|
|
case C_QUIET:
|
|
CHAR(_T('@'));
|
|
RECURSE(Cmd->Subcommands);
|
|
break;
|
|
case C_BLOCK:
|
|
CHAR(_T('('));
|
|
for (Sub = Cmd->Subcommands; Sub; Sub = Sub->Next)
|
|
{
|
|
RECURSE(Sub);
|
|
if (Sub->Next)
|
|
CHAR(_T('&'));
|
|
}
|
|
CHAR(_T(')'));
|
|
break;
|
|
case C_MULTI:
|
|
case C_IFFAILURE:
|
|
case C_IFSUCCESS:
|
|
case C_PIPE:
|
|
Sub = Cmd->Subcommands;
|
|
RECURSE(Sub);
|
|
PRINTF(_T(" %s "), OpString[Cmd->Type - C_OP_LOWEST]);
|
|
RECURSE(Sub->Next);
|
|
break;
|
|
case C_IF:
|
|
STRING(_T("if"));
|
|
if (Cmd->If.Flags & IFFLAG_IGNORECASE)
|
|
STRING(_T(" /I"));
|
|
if (Cmd->If.Flags & IFFLAG_NEGATE)
|
|
STRING(_T(" not"));
|
|
if (Cmd->If.LeftArg && SubstituteForVars(Cmd->If.LeftArg, Buf))
|
|
PRINTF(_T(" %s"), Buf);
|
|
PRINTF(_T(" %s"), IfOperatorString[Cmd->If.Operator]);
|
|
if (!SubstituteForVars(Cmd->If.RightArg, Buf)) return NULL;
|
|
PRINTF(_T(" %s "), Buf);
|
|
Sub = Cmd->Subcommands;
|
|
RECURSE(Sub);
|
|
if (Sub->Next)
|
|
{
|
|
STRING(_T(" else "));
|
|
RECURSE(Sub->Next);
|
|
}
|
|
break;
|
|
case C_FOR:
|
|
STRING(_T("for"));
|
|
if (Cmd->For.Switches & FOR_DIRS) STRING(_T(" /D"));
|
|
if (Cmd->For.Switches & FOR_F) STRING(_T(" /F"));
|
|
if (Cmd->For.Switches & FOR_LOOP) STRING(_T(" /L"));
|
|
if (Cmd->For.Switches & FOR_RECURSIVE) STRING(_T(" /R"));
|
|
if (Cmd->For.Params)
|
|
PRINTF(_T(" %s"), Cmd->For.Params);
|
|
PRINTF(_T(" %%%c in (%s) do "), Cmd->For.Variable, Cmd->For.List);
|
|
RECURSE(Cmd->Subcommands);
|
|
break;
|
|
}
|
|
|
|
for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
|
|
{
|
|
if (!SubstituteForVars(Redir->Filename, Buf))
|
|
return NULL;
|
|
PRINTF(_T(" %c%s%s"), _T('0') + Redir->Number,
|
|
RedirString[Redir->Mode], Buf);
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
VOID
|
|
FreeCommand(PARSED_COMMAND *Cmd)
|
|
{
|
|
if (Cmd->Subcommands)
|
|
FreeCommand(Cmd->Subcommands);
|
|
if (Cmd->Next)
|
|
FreeCommand(Cmd->Next);
|
|
FreeRedirection(Cmd->Redirections);
|
|
if (Cmd->Type == C_IF)
|
|
{
|
|
cmd_free(Cmd->If.LeftArg);
|
|
cmd_free(Cmd->If.RightArg);
|
|
}
|
|
else if (Cmd->Type == C_FOR)
|
|
{
|
|
cmd_free(Cmd->For.Params);
|
|
cmd_free(Cmd->For.List);
|
|
}
|
|
cmd_free(Cmd);
|
|
}
|