cmd: Separate IF command parsing from execution.

Introducting CMD_IF_CONDITION to hold IF condition.

Signed-off-by: Eric Pouech <epouech@codeweavers.com>
This commit is contained in:
Eric Pouech 2024-04-21 09:16:38 +02:00 committed by Alexandre Julliard
parent 5388414923
commit 1890a3de3f
3 changed files with 266 additions and 193 deletions

View file

@ -2739,171 +2739,6 @@ void WCMD_popd (void) {
LocalFree (temp);
}
/*******************************************************************
* evaluate_if_comparison
*
* Evaluates an "if" comparison operation
*
* PARAMS
* leftOperand [I] left operand, non NULL
* operator [I] "if" binary comparison operator, non NULL
* rightOperand [I] right operand, non NULL
* caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
*
* RETURNS
* Success: 1 if operator applied to the operands evaluates to TRUE
* 0 if operator applied to the operands evaluates to FALSE
* Failure: -1 if operator is not recognized
*/
static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
const WCHAR *rightOperand, int caseInsensitive)
{
WCHAR *endptr_leftOp, *endptr_rightOp;
long int leftOperand_int, rightOperand_int;
BOOL int_operands;
/* == is a special case, as it always compares strings */
if (!lstrcmpiW(operator, L"=="))
return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
: lstrcmpW (leftOperand, rightOperand) == 0;
/* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0);
rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0);
int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
/* Perform actual (integer or string) comparison */
if (!lstrcmpiW(operator, L"lss")) {
if (int_operands)
return leftOperand_int < rightOperand_int;
else
return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
: lstrcmpW (leftOperand, rightOperand) < 0;
}
if (!lstrcmpiW(operator, L"leq")) {
if (int_operands)
return leftOperand_int <= rightOperand_int;
else
return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
: lstrcmpW (leftOperand, rightOperand) <= 0;
}
if (!lstrcmpiW(operator, L"equ")) {
if (int_operands)
return leftOperand_int == rightOperand_int;
else
return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
: lstrcmpW (leftOperand, rightOperand) == 0;
}
if (!lstrcmpiW(operator, L"neq")) {
if (int_operands)
return leftOperand_int != rightOperand_int;
else
return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
: lstrcmpW (leftOperand, rightOperand) != 0;
}
if (!lstrcmpiW(operator, L"geq")) {
if (int_operands)
return leftOperand_int >= rightOperand_int;
else
return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
: lstrcmpW (leftOperand, rightOperand) >= 0;
}
if (!lstrcmpiW(operator, L"gtr")) {
if (int_operands)
return leftOperand_int > rightOperand_int;
else
return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
: lstrcmpW (leftOperand, rightOperand) > 0;
}
return -1;
}
int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate)
{
WCHAR condition[MAX_PATH];
int caseInsensitive = (wcsstr(quals, L"/I") != NULL);
*negate = !lstrcmpiW(param1,L"not");
lstrcpyW(condition, (*negate ? param2 : param1));
WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
if (!lstrcmpiW(condition, L"errorlevel")) {
WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
WCHAR *endptr;
int param_int = wcstol(param, &endptr, 10);
if (endptr == param || *endptr) goto syntax_err;
*test = (errorlevel >= param_int);
WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
}
else if (!lstrcmpiW(condition, L"exist")) {
WIN32_FIND_DATAW fd;
HANDLE hff;
WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
int len = lstrlenW(param);
if (!len) {
*test = FALSE;
} else {
/* FindFirstFile does not like a directory path ending in '\' or '/', so append a '.' */
if (param[len-1] == '\\' || param[len-1] == '/') wcscat(param, L".");
hff = FindFirstFileW(param, &fd);
*test = (hff != INVALID_HANDLE_VALUE);
if (*test) FindClose(hff);
}
WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
}
else if (!lstrcmpiW(condition, L"defined")) {
*test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE),
NULL, 0) > 0);
WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
}
else { /* comparison operation */
WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
WCHAR *paramStart;
lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, &paramStart, TRUE, FALSE));
if (!*leftOperand)
goto syntax_err;
/* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
p = paramStart + lstrlenW(leftOperand);
while (*p == ' ' || *p == '\t')
p++;
if (!wcsncmp(p, L"==", lstrlenW(L"==")))
lstrcpyW(operator, L"==");
else {
lstrcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
if (!*operator) goto syntax_err;
}
p += lstrlenW(operator);
lstrcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
if (!*rightOperand)
goto syntax_err;
*test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
if (*test == -1)
goto syntax_err;
p = paramStart + lstrlenW(rightOperand);
WCMD_parameter(p, 0, command, FALSE, FALSE);
}
return 1;
syntax_err:
return -1;
}
/****************************************************************************
* WCMD_if
*
@ -2920,26 +2755,28 @@ syntax_err:
*/
void WCMD_if (WCHAR *p, CMD_NODE **cmdList)
{
int negate; /* Negate condition */
int test; /* Condition evaluation result */
WCHAR *command;
CMD_IF_CONDITION if_cond;
WCHAR *command;
int test;
/* Function evaluate_if_condition relies on the global variables quals, param1 and param2
set in a call to WCMD_parse before */
if (evaluate_if_condition(p, &command, &test, &negate) == -1)
goto syntax_err;
if (if_condition_create(p, &command, &if_cond))
{
TRACE("%s\n", debugstr_if_condition(&if_cond));
if (if_condition_evaluate(&if_cond, &test))
{
WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
wine_dbgstr_w(param2), wine_dbgstr_w(command));
WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
wine_dbgstr_w(param2), wine_dbgstr_w(command));
/* Process rest of IF statement which is on the same line
Note: This may process all or some of the cmdList (eg a GOTO) */
WCMD_part_execute(cmdList, command, TRUE, test);
}
if_condition_dispose(&if_cond);
return;
}
/* Process rest of IF statement which is on the same line
Note: This may process all or some of the cmdList (eg a GOTO) */
WCMD_part_execute(cmdList, command, TRUE, (test != negate));
return;
syntax_err:
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
}
/****************************************************************************

View file

@ -60,6 +60,29 @@ typedef enum _CMD_OPERATOR
/* Data structure to hold commands to be processed */
enum cond_operator {CMD_IF_ERRORLEVEL, CMD_IF_EXIST, CMD_IF_DEFINED,
CMD_IF_BINOP_EQUAL /* == */, CMD_IF_BINOP_LSS, CMD_IF_BINOP_LEQ, CMD_IF_BINOP_EQU,
CMD_IF_BINOP_NEQ, CMD_IF_BINOP_GEQ, CMD_IF_BINOP_GTR};
typedef struct _CMD_IF_CONDITION
{
unsigned case_insensitive : 1,
negated : 1,
op;
union
{
/* CMD_IF_ERRORLEVEL */
int level;
/* CMD_IF_EXIST, CMD_IF_DEFINED */
const WCHAR *operand;
/* CMD_BINOP_EQUAL, CMD_BINOP_LSS, CMD_BINOP_LEQ, CMD_BINOP_EQU, CMD_BINOP_NEQ, CMD_BINOP_GEQ, CMD_BINOP_GTR */
struct
{
const WCHAR *left;
const WCHAR *right;
};
};
} CMD_IF_CONDITION;
typedef struct _CMD_COMMAND
{
WCHAR *command; /* Command string to execute */
@ -100,6 +123,12 @@ static inline int CMD_node_get_depth(const CMD_NODE *node)
}
/* end temporary */
/* temporary helpers for parsing transition */
BOOL if_condition_create(WCHAR *start, WCHAR **end, CMD_IF_CONDITION *cond);
void if_condition_dispose(CMD_IF_CONDITION *);
BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test);
const char *debugstr_if_condition(const CMD_IF_CONDITION *cond);
void WCMD_assoc (const WCHAR *, BOOL);
void WCMD_batch (WCHAR *, WCHAR *, BOOL, WCHAR *, HANDLE);
void WCMD_call (WCHAR *command);

View file

@ -1035,6 +1035,151 @@ static CMD_NODE *node_create_binary(CMD_OPERATOR op, CMD_NODE *l, CMD_NODE *r)
return new;
}
void if_condition_dispose(CMD_IF_CONDITION *cond)
{
switch (cond->op)
{
case CMD_IF_ERRORLEVEL:
break;
case CMD_IF_EXIST:
case CMD_IF_DEFINED:
free((void*)cond->operand);
break;
case CMD_IF_BINOP_EQUAL:
case CMD_IF_BINOP_LSS:
case CMD_IF_BINOP_LEQ:
case CMD_IF_BINOP_EQU:
case CMD_IF_BINOP_NEQ:
case CMD_IF_BINOP_GEQ:
case CMD_IF_BINOP_GTR:
free((void*)cond->left);
free((void*)cond->right);
break;
}
}
BOOL if_condition_create(WCHAR *start, WCHAR **end, CMD_IF_CONDITION *cond)
{
WCHAR *param_start;
const WCHAR *param_copy;
int narg = 0;
if (cond) memset(cond, 0, sizeof(*cond));
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
/* /I is the only option supported */
if (!wcsicmp(param_copy, L"/I"))
{
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
if (cond) cond->case_insensitive = 1;
}
if (!wcsicmp(param_copy, L"NOT"))
{
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
if (cond) cond->negated = 1;
}
if (!wcsicmp(param_copy, L"errorlevel"))
{
WCHAR *endptr;
int level;
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
if (cond) cond->op = CMD_IF_ERRORLEVEL;
level = wcstol(param_copy, &endptr, 10);
if (*endptr) return FALSE;
if (cond) cond->level = level;
}
else if (!wcsicmp(param_copy, L"exist"))
{
param_copy = WCMD_parameter(start, narg++, &param_start, FALSE, FALSE);
if (cond) cond->op = CMD_IF_EXIST;
if (cond) cond->operand = wcsdup(param_copy);
}
else if (!wcsicmp(param_copy, L"defined"))
{
param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
if (cond) cond->op = CMD_IF_DEFINED;
if (cond) cond->operand = wcsdup(param_copy);
}
else /* comparison operation */
{
if (*param_copy == L'\0') return FALSE;
param_copy = WCMD_parameter(start, narg - 1, &param_start, TRUE, FALSE);
if (cond) cond->left = wcsdup(param_copy);
start = WCMD_skip_leading_spaces(param_start + wcslen(param_copy));
/* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
if (start[0] == L'=' && start[1] == L'=')
{
start += 2; /* == */
if (cond) cond->op = CMD_IF_BINOP_EQUAL;
}
else
{
static struct
{
const WCHAR *name;
enum cond_operator binop;
}
allowed_operators[] = {{L"lss", CMD_IF_BINOP_LSS},
{L"leq", CMD_IF_BINOP_LEQ},
{L"equ", CMD_IF_BINOP_EQU},
{L"neq", CMD_IF_BINOP_NEQ},
{L"geq", CMD_IF_BINOP_GEQ},
{L"gtr", CMD_IF_BINOP_GTR},
};
int i;
param_copy = WCMD_parameter(start, 0, &param_start, FALSE, FALSE);
for (i = 0; i < ARRAY_SIZE(allowed_operators); i++)
if (!wcsicmp(param_copy, allowed_operators[i].name)) break;
if (i == ARRAY_SIZE(allowed_operators))
{
if (cond) free((void*)cond->left);
return FALSE;
}
if (cond) cond->op = allowed_operators[i].binop;
start += wcslen(param_copy);
}
param_copy = WCMD_parameter(start, 0, &param_start, TRUE, FALSE);
if (*param_copy == L'\0')
{
if (cond) free((void*)cond->left);
return FALSE;
}
if (cond) cond->right = wcsdup(param_copy);
start = param_start + wcslen(param_copy);
narg = 0;
}
/* check all remaning args are present, and compute pointer to end of condition */
param_copy = WCMD_parameter(start, narg, end, TRUE, FALSE);
return cond || *param_copy != L'\0';
}
const char *debugstr_if_condition(const CMD_IF_CONDITION *cond)
{
const char *header = wine_dbg_sprintf("{{%s%s", cond->negated ? "not " : "", cond->case_insensitive ? "nocase " : "");
switch (cond->op)
{
case CMD_IF_ERRORLEVEL: return wine_dbg_sprintf("%serrorlevel %d}}", header, cond->level);
case CMD_IF_EXIST: return wine_dbg_sprintf("%sexist %ls}}", header, cond->operand);
case CMD_IF_DEFINED: return wine_dbg_sprintf("%sdefined %ls}}", header, cond->operand);
case CMD_IF_BINOP_EQUAL: return wine_dbg_sprintf("%s%ls == %ls}}", header, cond->left, cond->right);
case CMD_IF_BINOP_LSS: return wine_dbg_sprintf("%s%ls LSS %ls}}", header, cond->left, cond->right);
case CMD_IF_BINOP_LEQ: return wine_dbg_sprintf("%s%ls LEQ %ls}}", header, cond->left, cond->right);
case CMD_IF_BINOP_EQU: return wine_dbg_sprintf("%s%ls EQU %ls}}", header, cond->left, cond->right);
case CMD_IF_BINOP_NEQ: return wine_dbg_sprintf("%s%ls NEQ %ls}}", header, cond->left, cond->right);
case CMD_IF_BINOP_GEQ: return wine_dbg_sprintf("%s%ls GEQ %ls}}", header, cond->left, cond->right);
case CMD_IF_BINOP_GTR: return wine_dbg_sprintf("%s%ls GTR %ls}}", header, cond->left, cond->right);
default:
FIXME("Unexpected condition operator %u\n", cond->op);
return "{{}}";
}
}
static void init_msvcrt_io_block(STARTUPINFOW* st)
{
STARTUPINFOW st_p;
@ -2092,20 +2237,12 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE
To be able to handle ('s in the condition part take as much as evaluate_if_condition
would take and skip parsing it here. */
} else if (WCMD_keyword_ws_found(L"if", curPos)) {
int negate; /* Negate condition */
int test; /* Condition evaluation result */
WCHAR *p, *command;
inIf = TRUE;
p = curPos+(lstrlenW(L"if"));
while (*p == ' ' || *p == '\t')
p++;
WCMD_parse (p, quals, param1, param2);
/* Function evaluate_if_condition relies on the global variables quals, param1 and param2
set in a call to WCMD_parse before */
if (evaluate_if_condition(p, &command, &test, &negate) != -1)
p = WCMD_skip_leading_spaces(curPos + 2); /* "if" */
if (if_condition_create(p, &command, NULL))
{
int if_condition_len = command - curPos;
WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n",
@ -2115,7 +2252,6 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE
(*curLen)+=if_condition_len;
curPos+=if_condition_len;
}
if (WCMD_keyword_ws_found(L"set", curPos))
ignoreBracket = TRUE;
@ -2503,6 +2639,77 @@ WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE
return extraSpace;
}
BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test)
{
int (WINAPI *cmp)(const WCHAR*, const WCHAR*) = cond->case_insensitive ? lstrcmpiW : lstrcmpW;
TRACE("About to evaluate condition %s\n", debugstr_if_condition(cond));
*test = 0;
switch (cond->op)
{
case CMD_IF_ERRORLEVEL:
*test = errorlevel >= cond->level;
break;
case CMD_IF_EXIST:
{
WIN32_FIND_DATAW fd;
HANDLE hff;
size_t len = wcslen(cond->operand);
if (len)
{
/* FindFirstFile does not like a directory path ending in '\' or '/', so append a '.' */
if (cond->operand[len - 1] == '\\' || cond->operand[len - 1] == '/')
{
WCHAR *new = xrealloc((void*)cond->operand, (wcslen(cond->operand) + 2) * sizeof(WCHAR));
wcscat(new, L".");
cond->operand = new;
}
hff = FindFirstFileW(cond->operand, &fd);
*test = (hff != INVALID_HANDLE_VALUE);
if (*test) FindClose(hff);
}
}
break;
case CMD_IF_DEFINED:
*test = GetEnvironmentVariableW(cond->operand, NULL, 0) > 0;
break;
case CMD_IF_BINOP_EQUAL:
/* == is a special case, as it always compares strings */
*test = (*cmp)(cond->left, cond->right) == 0;
break;
default:
{
int left_int, right_int;
WCHAR *end_left, *end_right;
int cmp_val;
/* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
left_int = wcstol(cond->left, &end_left, 0);
right_int = wcstol(cond->right, &end_right, 0);
if (end_left > cond->left && !*end_left && end_right > cond->right && !*end_right)
cmp_val = left_int - right_int;
else
cmp_val = (*cmp)(cond->left, cond->right);
switch (cond->op)
{
case CMD_IF_BINOP_LSS: *test = cmp_val < 0; break;
case CMD_IF_BINOP_LEQ: *test = cmp_val <= 0; break;
case CMD_IF_BINOP_EQU: *test = cmp_val == 0; break;
case CMD_IF_BINOP_NEQ: *test = cmp_val != 0; break;
case CMD_IF_BINOP_GEQ: *test = cmp_val >= 0; break;
case CMD_IF_BINOP_GTR: *test = cmp_val > 0; break;
default:
FIXME("Unexpected comparison operator %u\n", cond->op);
return FALSE;
}
}
break;
}
if (cond->negated) *test ^= 1;
return TRUE;
}
/***************************************************************************
* WCMD_process_commands
*