- Make IF command a special form; necessary to make nested multi-line IF blocks work properly

- Implement IF /I option, IF CMDEXTVERSION, and generic comparisons (EQU etc)
- Make IF ERRORLEVEL return true if the errorlevel is greater than the number
- Remove hacked support for multi-line IF blocks from batch.c

svn path=/trunk/; revision=38280
This commit is contained in:
Jeffrey Morlan 2008-12-22 22:34:51 +00:00
parent 940dd12ea9
commit 781ae61f03
7 changed files with 289 additions and 224 deletions

View file

@ -269,7 +269,6 @@ BOOL Batch (LPTSTR fullname, LPTSTR firstword, LPTSTR param, BOOL forcenew)
SetFilePointer (bc->hBatchFile, 0, NULL, FILE_BEGIN);
bc->bEcho = bEcho; /* Preserve echo across batch calls */
bc->shiftlevel = 0;
bc->bCmdBlock = -1;
bc->ffind = NULL;
bc->forvar = _T('\0');
@ -445,39 +444,6 @@ LPTSTR ReadBatchLine ()
first = textline;
/* cmd block over multiple lines (..) */
if (bc->bCmdBlock >= 0)
{
if (*first == _T(')'))
{
first++;
/* Strip leading spaces and trailing space/control chars */
while(_istspace (*first))
first++;
if ((_tcsncicmp (first, _T("else"), 4) == 0) && (_tcschr(first, _T('('))))
{
bc->bExecuteBlock[bc->bCmdBlock] = !bc->bExecuteBlock[bc->bCmdBlock];
}
else
{
bc->bCmdBlock--;
}
continue;
}
if (bc->bCmdBlock < MAX_PATH)
if (!bc->bExecuteBlock[bc->bCmdBlock])
{
/* increase the bCmdBlock count when there is another conditon which opens a new bracket */
if ((_tcsncicmp (first, _T("if"), 2) == 0) && _tcschr(first, _T('(')))
{
bc->bCmdBlock++;
if ((bc->bCmdBlock > 0) && (bc->bCmdBlock < MAX_PATH))
bc->bExecuteBlock[bc->bCmdBlock] = bc->bExecuteBlock[bc->bCmdBlock - 1];
}
continue;
}
}
break;
}

View file

@ -21,8 +21,6 @@ typedef struct tagBATCHCONTEXT
HANDLE hFind; /* Preserve find handle when doing a for */
REDIRECTION *RedirList;
TCHAR forvar;
INT bCmdBlock;
BOOL bExecuteBlock[MAX_PATH];
} BATCH_CONTEXT, *LPBATCH_CONTEXT;

View file

@ -879,7 +879,7 @@ ExecuteCommand(PARSED_COMMAND *Cmd)
if(bc)
bNewBatch = FALSE;
Success = DoCommand(Cmd->CommandLine);
Success = DoCommand(Cmd->Command.CommandLine);
if(bNewBatch && bc)
AddBatchRedirection(&Cmd->Redirections);
@ -903,6 +903,9 @@ ExecuteCommand(PARSED_COMMAND *Cmd)
case C_PIPE:
ExecutePipeline(Cmd);
break;
case C_IF:
Success = ExecuteIf(Cmd);
break;
}
UndoRedirection(Cmd->Redirections, NULL);

View file

@ -258,6 +258,15 @@ INT CommandHistory (LPTSTR param);
#endif
/* Prototypes for IF.C */
#define IFFLAG_NEGATE 1 /* NOT */
#define IFFLAG_IGNORECASE 2 /* /I */
enum { IF_CMDEXTVERSION, IF_DEFINED, IF_ERRORLEVEL, IF_EXIST,
IF_STRINGEQ, /* == */
IF_EQU, IF_GTR, IF_GEQ, IF_LSS, IF_LEQ, IF_NEQ };
BOOL ExecuteIf(struct _PARSED_COMMAND *Cmd);
/* Prototypes for INTERNAL.C */
VOID InitLastPath (VOID);
VOID FreeLastPath (VOID);
@ -330,15 +339,28 @@ INT CommandMsgbox (LPTSTR);
/* Prototypes from PARSER.C */
enum { C_COMMAND, C_QUIET, C_BLOCK, C_MULTI, C_IFFAILURE, C_IFSUCCESS, C_PIPE };
enum { C_COMMAND, C_QUIET, C_BLOCK, C_MULTI, C_IFFAILURE, C_IFSUCCESS, C_PIPE, C_IF };
typedef struct _PARSED_COMMAND
{
struct _PARSED_COMMAND *Subcommands;
struct _PARSED_COMMAND *Next;
struct _REDIRECTION *Redirections;
TCHAR *Tail;
BYTE Type;
TCHAR CommandLine[];
union
{
struct
{
TCHAR *Tail;
TCHAR CommandLine[];
} Command;
struct
{
BYTE Flags;
BYTE Operator;
TCHAR *LeftArg;
TCHAR *RightArg;
} If;
};
} PARSED_COMMAND;
PARSED_COMMAND *ParseCommand(LPTSTR Line);
VOID EchoCommand(PARSED_COMMAND *Cmd);

View file

@ -120,7 +120,7 @@ COMMAND cmds[] =
{_T("history"), 0, CommandHistory},
#endif
{_T("if"), 0, cmd_if},
// {_T("if"), 0, cmd_if},
#ifdef INCLUDE_CMD_LABEL
{_T("label"), 0, cmd_label},

View file

@ -32,15 +32,30 @@
#include <precomp.h>
#define X_EXEC 1
#define X_EMPTY 0x80
INT cmd_if (LPTSTR param)
static INT GenericCmp(INT (*StringCmp)(LPCTSTR, LPCTSTR),
LPCTSTR Left, LPCTSTR Right)
{
INT x_flag = 0; /* when set cause 'then' clause to be executed */
LPTSTR pp;
TCHAR *end;
INT nLeft = _tcstol(Left, &end, 0);
if (*end == _T('\0'))
{
INT nRight = _tcstol(Right, &end, 0);
if (*end == _T('\0'))
{
/* both arguments are numeric */
return (nLeft < nRight) ? -1 : (nLeft > nRight);
}
}
return StringCmp(Left, Right);
}
BOOL ExecuteIf(PARSED_COMMAND *Cmd)
{
INT result = FALSE; /* when set cause 'then' clause to be executed */
LPTSTR param;
#if 0
/* FIXME: need to handle IF /?; will require special parsing */
TRACE ("cmd_if: (\'%s\')\n", debugstr_aw(param));
if (!_tcsncmp (param, _T("/?"), 2))
@ -48,178 +63,89 @@ INT cmd_if (LPTSTR param)
ConOutResPaging(TRUE,STRING_IF_HELP1);
return 0;
}
#endif
/* First check if param string begins with 'not' */
if (!_tcsnicmp (param, _T("not"), 3) && _istspace (*(param + 3)))
if (Cmd->If.Operator == IF_CMDEXTVERSION)
{
x_flag = X_EXEC; /* Remember 'NOT' */
param += 3; /* Step over 'NOT' */
while (_istspace (*param)) /* And subsequent spaces */
param++;
/* IF CMDEXTVERSION n: check if Command Extensions version
* is greater or equal to n */
DWORD n = _tcstoul(Cmd->If.RightArg, &param, 10);
if (*param != _T('\0'))
{
error_syntax(Cmd->If.RightArg);
return FALSE;
}
result = (2 >= n);
}
/* Check for 'exist' form */
if (!_tcsnicmp (param, _T("exist"), 5) && _istspace (*(param + 5)))
else if (Cmd->If.Operator == IF_DEFINED)
{
UINT i;
BOOL bInside = FALSE;
param += 5;
while (_istspace (*param))
param++;
pp = param;
/* find the whole path to the file */
for(i = 0; i < _tcslen(param); i++)
{
if(param[i] == _T('\"'))
bInside = !bInside;
if((param[i] == _T(' ')) && !bInside)
{
break;
}
pp++;
}
*pp++ = _T('\0');
i = 0;
/* remove quotes */
while(i < _tcslen(param))
{
if(param[i] == _T('\"'))
memmove(&param[i],&param[i + 1], _tcslen(&param[i]) * sizeof(TCHAR));
else
i++;
}
if (*pp)
{
WIN32_FIND_DATA f;
HANDLE hFind;
hFind = FindFirstFile (param, &f);
x_flag ^= (hFind == INVALID_HANDLE_VALUE) ? 0 : X_EXEC;
if (hFind != INVALID_HANDLE_VALUE)
{
FindClose (hFind);
}
}
else
return 0;
/* IF DEFINED var: check if environment variable exists */
result = (GetEnvVarOrSpecial(Cmd->If.RightArg) != NULL);
}
else if (!_tcsnicmp (param, _T("defined"), 7) && _istspace (*(param + 7)))
else if (Cmd->If.Operator == IF_ERRORLEVEL)
{
/* Check for 'defined' form */
TCHAR Value [1];
INT ValueSize = 0;
param += 7;
/* IF [NOT] DEFINED var COMMAND */
/* ^ */
while (_istspace (*param))
param++;
/* IF [NOT] DEFINED var COMMAND */
/* ^ */
pp = param;
while (*pp && !_istspace (*pp))
pp++;
/* IF [NOT] DEFINED var COMMAND */
/* ^ */
if (*pp)
/* IF ERRORLEVEL n: check if last exit code is greater or equal to n */
INT n = _tcstol(Cmd->If.RightArg, &param, 10);
if (*param != _T('\0'))
{
*pp++ = _T('\0');
ValueSize = GetEnvironmentVariable(param, Value, sizeof(Value) / sizeof(Value[0]));
x_flag ^= (0 == ValueSize)
? 0
: X_EXEC;
x_flag |= X_EMPTY;
error_syntax(Cmd->If.RightArg);
return FALSE;
}
else
return 0;
result = (nErrorLevel >= n);
}
else if (!_tcsnicmp (param, _T("errorlevel"), 10) && _istspace (*(param + 10)))
else if (Cmd->If.Operator == IF_EXIST)
{
/* Check for 'errorlevel' form */
INT n = 0;
/* IF EXIST filename: check if file exists (wildcards allowed) */
WIN32_FIND_DATA f;
HANDLE hFind;
pp = param + 10;
while (_istspace (*pp))
pp++;
StripQuotes(Cmd->If.RightArg);
while (_istdigit (*pp))
n = n * 10 + (*pp++ - _T('0'));
x_flag ^= (nErrorLevel != n) ? 0 : X_EXEC;
x_flag |= X_EMPTY; /* Syntax error if comd empty */
hFind = FindFirstFile(Cmd->If.RightArg, &f);
if (hFind != INVALID_HANDLE_VALUE)
{
result = TRUE;
FindClose(hFind);
}
}
else
{
BOOL bInQuote = FALSE;
INT p1len;
pp = param;
while ( *pp && ( bInQuote || *pp != _T('=') ) )
{
if ( *pp == _T('\"') )
bInQuote = !bInQuote;
++pp;
}
p1len = pp-param;
/* check for "==" */
if ( *pp++ != _T('=') || *pp++ != _T('=') )
{
error_syntax ( NULL );
return 1;
}
while (_istspace (*pp)) /* Skip subsequent spaces */
pp++;
/* Do case-insensitive string comparisons if /I specified */
INT (*StringCmp)(LPCTSTR, LPCTSTR) =
(Cmd->If.Flags & IFFLAG_IGNORECASE) ? _tcsicmp : _tcscmp;
/* are the two sides equal*/
if ( !_tcsncmp(param,pp,p1len))
x_flag ^= X_EXEC;
pp += p1len;
if ( x_flag )
if (Cmd->If.Operator == IF_STRINGEQ)
{
x_flag |= X_EMPTY;
/* IF str1 == str2 */
result = StringCmp(Cmd->If.LeftArg, Cmd->If.RightArg) == 0;
}
else
{
result = GenericCmp(StringCmp, Cmd->If.LeftArg, Cmd->If.RightArg);
switch (Cmd->If.Operator)
{
case IF_EQU: result = (result == 0); break;
case IF_NEQ: result = (result != 0); break;
case IF_LSS: result = (result < 0); break;
case IF_LEQ: result = (result <= 0); break;
case IF_GTR: result = (result > 0); break;
case IF_GEQ: result = (result >= 0); break;
}
}
}
while (_istspace (*pp)) /* skip spaces */
pp++;
if (*pp == _T('('))
if (result ^ ((Cmd->If.Flags & IFFLAG_NEGATE) != 0))
{
if (bc)
{
pp++;
bc->bCmdBlock++;
if ((bc->bCmdBlock >= 0) && (bc->bCmdBlock < MAX_PATH))
bc->bExecuteBlock[bc->bCmdBlock] = x_flag & X_EXEC;
/* commands are in the next lines */
if (*pp == _T('\0'))
return 0;
}
/* full condition was true, do the command */
return ExecuteCommand(Cmd->Subcommands);
}
if (x_flag & X_EMPTY)
else
{
while (_istspace (*pp)) /* Then skip spaces */
pp++;
if (*pp == _T('\0')) /* If nothing left then syntax err */
{
error_syntax (NULL);
return 1;
}
/* full condition was false, do the "else" command if there is one */
if (Cmd->Subcommands->Next)
return ExecuteCommand(Cmd->Subcommands->Next);
return TRUE;
}
if (x_flag & X_EXEC)
{
ParseCommandLine (pp);
}
return 0;
}
/* EOF */

View file

@ -1,3 +1,8 @@
/*
* PARSER.C - command parsing.
*
*/
#include <precomp.h>
#define C_OP_LOWEST C_MULTI
@ -6,10 +11,28 @@ 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)
{
/* These three characters act like spaces to the parser */
return _istspace(Char) || (Char && _tcschr(_T(",;="), Char));
return _istspace(Char) || (Char && _tcschr(STANDARD_SEPS, Char));
}
enum { TOK_END, TOK_NORMAL, TOK_OPERATOR, TOK_REDIRECTION,
@ -70,36 +93,44 @@ static void ParseError()
/* 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, BOOL PreserveSpace)
static int ParseToken(TCHAR ExtraEnd, TCHAR *Separators)
{
TCHAR *Out = CurrentToken;
TCHAR Char = CurChar;
TCHAR Char;
int Type;
BOOL bInQuote = FALSE;
if (!PreserveSpace)
{
while (Char != _T('\n') && IsSeparator(Char))
Char = ParseChar();
}
while (Char && Char != _T('\n'))
for (Char = CurChar; Char && Char != _T('\n'); Char = ParseChar())
{
bInQuote ^= (Char == _T('"'));
if (!bInQuote)
{
/* Check for all the myriad ways in which this token
* may be brought to an untimely end. */
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('>')))
|| _tcschr(_T(")&|<>") + (InsideBlock ? 0 : 1), Char)
|| (!PreserveSpace && IsSeparator(Char))
|| (Char == ExtraEnd))
&& (*ParsePos == _T('<') || *ParsePos == _T('>'))))
{
break;
}
if (Char == ExtraEnd)
break;
if (InsideBlock && Char == _T(')'))
break;
if (_tcschr(_T("&|<>"), Char))
break;
if (Char == _T('^'))
{
Char = ParseChar();
@ -112,7 +143,6 @@ static int ParseToken(TCHAR ExtraEnd, BOOL PreserveSpace)
if (Out == &CurrentToken[CMDLINE_LENGTH - 1])
break;
*Out++ = Char;
Char = ParseChar();
}
/* Check if we got at least one character before reaching a special one.
@ -214,7 +244,7 @@ static BOOL ParseRedirection(REDIRECTION **List)
if (!*Tok)
{
/* The file name was not part of this token, so it'll be the next one */
if (ParseToken(0, FALSE) != TOK_NORMAL)
if (ParseToken(0, STANDARD_SEPS) != TOK_NORMAL)
goto fail;
Tok = CurrentToken;
}
@ -284,7 +314,7 @@ static PARSED_COMMAND *ParseBlock(REDIRECTION *RedirList)
InsideBlock--;
/* Process any trailing redirections */
while (ParseToken(0, FALSE) == TOK_REDIRECTION)
while (ParseToken(0, STANDARD_SEPS) == TOK_REDIRECTION)
{
if (!ParseRedirection(&Cmd->Redirections))
{
@ -295,6 +325,94 @@ static PARSED_COMMAND *ParseBlock(REDIRECTION *RedirList)
return Cmd;
}
/* Parse an IF statement */
static PARSED_COMMAND *ParseIf(void)
{
PARSED_COMMAND *Cmd = cmd_alloc(sizeof(PARSED_COMMAND));
memset(Cmd, 0, sizeof(PARSED_COMMAND));
Cmd->Type = C_IF;
int Type = ParseToken(0, STANDARD_SEPS);
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;
}
static PARSED_COMMAND *ParseCommandPart(void)
{
TCHAR ParsedLine[CMDLINE_LENGTH];
@ -318,7 +436,7 @@ static PARSED_COMMAND *ParseCommandPart(void)
{
/* "Ignore" the rest of the line.
* (Line continuations will still be parsed, though.) */
while (ParseToken(0, TRUE) != TOK_END)
while (ParseToken(0, NULL) != TOK_END)
;
return NULL;
}
@ -339,7 +457,7 @@ static PARSED_COMMAND *ParseCommandPart(void)
/* Get the head of the command */
while (1)
{
Type = ParseToken(_T('('), FALSE);
Type = ParseToken(_T('('), STANDARD_SEPS);
if (Type == TOK_NORMAL)
{
Pos = _stpcpy(ParsedLine, CurrentToken);
@ -367,12 +485,22 @@ static PARSED_COMMAND *ParseCommandPart(void)
}
TailOffset = Pos - ParsedLine;
/* FIXME: FOR, IF, and REM need special processing by the parser. */
/* Check for special forms */
if (_tcsicmp(ParsedLine, _T("if")) == 0)
{
if (RedirList)
{
ParseError();
FreeRedirection(RedirList);
return NULL;
}
return ParseIf();
}
/* Now get the tail */
while (1)
{
Type = ParseToken(0, TRUE);
Type = ParseToken(0, NULL);
if (Type == TOK_NORMAL)
{
if (Pos + _tcslen(CurrentToken) >= &ParsedLine[CMDLINE_LENGTH])
@ -394,13 +522,13 @@ static PARSED_COMMAND *ParseCommandPart(void)
}
}
Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, CommandLine[Pos + 1 - ParsedLine]));
Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.CommandLine[Pos + 1 - ParsedLine]));
Cmd->Type = C_COMMAND;
Cmd->Next = NULL;
Cmd->Subcommands = NULL;
Cmd->Redirections = RedirList;
_tcscpy(Cmd->CommandLine, ParsedLine);
Cmd->Tail = Cmd->CommandLine + TailOffset;
_tcscpy(Cmd->Command.CommandLine, ParsedLine);
Cmd->Command.Tail = Cmd->Command.CommandLine + TailOffset;
return Cmd;
}
@ -487,7 +615,7 @@ EchoCommand(PARSED_COMMAND *Cmd)
switch (Cmd->Type)
{
case C_COMMAND:
ConOutPrintf(_T("%s"), Cmd->CommandLine);
ConOutPrintf(_T("%s"), Cmd->Command.CommandLine);
break;
case C_QUIET:
return;
@ -509,6 +637,23 @@ EchoCommand(PARSED_COMMAND *Cmd)
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)
ConOutPrintf(_T(" %s"), Cmd->If.LeftArg);
ConOutPrintf(_T(" %s %s "), IfOperatorString[Cmd->If.Operator], Cmd->If.RightArg);
Sub = Cmd->Subcommands;
EchoCommand(Sub);
if (Sub->Next)
{
ConOutPrintf(_T(" else "));
EchoCommand(Sub->Next);
}
break;
}
for (Redir = Cmd->Redirections; Redir; Redir = Redir->Next)
@ -526,5 +671,10 @@ FreeCommand(PARSED_COMMAND *Cmd)
if (Cmd->Next)
FreeCommand(Cmd->Next);
FreeRedirection(Cmd->Redirections);
if (Cmd->Type == C_IF)
{
cmd_free(Cmd->If.LeftArg);
cmd_free(Cmd->If.RightArg);
}
cmd_free(Cmd);
}