[CMD] SET: Diverse fixes for the arithmetic-expression parser (/A option).

- Detect whether a division by zero is done, and fail if so.

- Detect whether an invalid number is provided:
  * If _tcstol() fails with errno == ERANGE, we've got an overflow or
    underflow.
  * If the next character where _tcstol() is not a whitespace but is a
    character compatible with the first character of an identifier, the
    number is invalid.

- Add + to the list of existing unary operators (!,~,-), and parse them
  where many of these are present. Indeed, expressions like: +3, -+-+3,
  !!-+3 (or with other unary ops, etc.) are valid.

- Operators constituted of more than one characters, can contain
  whitespace separating their constituting characters.
  Thus, "a + = 3" is equivalent to "a += 3" (and the same for -=, *=,
  /=, %=, &=, |= and ^=), and "a < < 3" is equivalent to "a << 3" (and
  the same for >>, <<= and >>=).

- After evaluating everything, if unparsed data remains, fail and bail out.

- Return Windows' CMD-compatible errorlevels.

See https://ss64.com/nt/set.html for more details.

Fixes some cmd_winetests.
This commit is contained in:
Hermès Bélusca-Maïto 2020-07-19 21:30:50 +02:00
parent f5cf67f455
commit 17e094cd34
No known key found for this signature in database
GPG key ID: 3B2539C65E7B93D0

View file

@ -112,14 +112,22 @@ INT cmd_set(LPTSTR param)
if (!_tcsnicmp(param, _T("/A"), 2))
{
BOOL Success;
/* Save error level since seta_eval() modifies it, as
* we need to set it later according to specific rules. */
INT nOldErrorLevel = nErrorLevel;
StripQuotes(param);
Success = seta_eval(skip_ws(param + 2));
if (!Success)
{
#if 0
/* Might seem random but this is what windows xp does -- This is a message ID */
retval = 9165;
#endif
retval = nErrorLevel;
nErrorLevel = nOldErrorLevel;
}
// return !Success;
else
{
retval = 0;
@ -287,12 +295,33 @@ calc(INT* lval, TCHAR op, INT rval)
case '*':
*lval *= rval;
break;
case '/':
{
if (rval == 0)
{
// FIXME: Localize
ConErrPuts(_T("Division by zero error.\n"));
nErrorLevel = 0x400023D1; // 1073750993;
return FALSE;
}
*lval /= rval;
break;
}
case '%':
{
if (rval == 0)
{
// FIXME: Localize
ConErrPuts(_T("Division by zero error.\n"));
nErrorLevel = 0x400023D1; // 1073750993;
return FALSE;
}
*lval %= rval;
break;
}
case '+':
*lval += rval;
break;
@ -308,8 +337,10 @@ calc(INT* lval, TCHAR op, INT rval)
case '|':
*lval |= rval;
break;
default:
ConErrResPuts(STRING_INVALID_OPERAND);
nErrorLevel = 0x400023CE; // 1073750990;
return FALSE;
}
return TRUE;
@ -322,23 +353,47 @@ static BOOL
seta_unaryTerm(LPCTSTR* p_, INT* result)
{
LPCTSTR p = *p_;
INT rval;
if (*p == _T('('))
{
INT rval;
p = skip_ws(p + 1);
if (!seta_stmt(&p, &rval))
return FALSE;
if (*p++ != _T(')'))
{
ConErrResPuts(STRING_EXPECTED_CLOSE_PAREN);
nErrorLevel = 0x400023CC; // 1073750988;
return FALSE;
}
*result = rval;
}
else if (isdigit(*p))
else if (_istdigit(*p))
{
*result = _tcstol(p, (LPTSTR*)&p, 0);
errno = 0;
rval = _tcstol(p, (LPTSTR*)&p, 0);
/* Check for overflow / underflow */
if (errno == ERANGE)
{
// FIXME: Localize
ConErrPuts(_T("Invalid number. Numbers are limited to 32-bits of precision.\n"));
nErrorLevel = 0x400023D0; // 1073750992;
return FALSE;
}
/*
* _tcstol() stopped at the first non-digit character. If it's not a whitespace,
* or if it's the start of a possible identifier, this means the number being
* interpreted was invalid.
*/
else if (*p && !_istspace(*p) && __iscsymf(*p))
{
// FIXME: Localize
ConErrPuts(_T("Invalid number. Numeric constants are either decimal (42), hexadecimal (0x2A), or octal (052).\n"));
nErrorLevel = 0x400023CF; // 1073750991;
return FALSE;
}
*result = rval;
}
else if (__iscsymf(*p))
{
@ -350,6 +405,7 @@ seta_unaryTerm(LPCTSTR* p_, INT* result)
else
{
ConErrResPuts(STRING_EXPECTED_NUMBER_OR_VARIABLE);
nErrorLevel = 0x400023CD; // 1073750989;
return FALSE;
}
*p_ = skip_ws(p);
@ -363,24 +419,36 @@ seta_mulTerm(LPCTSTR* p_, INT* result)
TCHAR op = 0;
INT rval;
if (_tcschr(_T("!~-"), *p))
if (_tcschr(_T("!~-+"), *p))
{
op = *p;
p = skip_ws(p + 1);
if (!seta_mulTerm(&p, &rval))
return FALSE;
switch (op)
{
case '!':
rval = !rval;
break;
case '~':
rval = ~rval;
break;
case '-':
rval = -rval;
break;
#if 0
case '+':
rval = rval;
break;
#endif
}
}
if (!seta_unaryTerm(&p, &rval))
return FALSE;
switch (op)
else
{
case '!':
rval = !rval;
break;
case '~':
rval = ~rval;
break;
case '-':
rval = -rval;
break;
if (!seta_unaryTerm(&p, &rval))
return FALSE;
}
*result = rval;
@ -442,12 +510,18 @@ seta_bitAndTerm(LPCTSTR* p_, INT* result)
return FALSE;
/* Handle << >> operators */
while (*p && _tcschr(_T("<>"), *p) && p[0] == p[1])
while (*p && _tcschr(_T("<>"), *p))
{
INT rval;
TCHAR op = *p;
p = skip_ws(p + 2);
/* Check whether the next non-whitespace character is the same operator */
p = skip_ws(p + 1);
if (*p != op)
break;
/* Skip it */
p = skip_ws(p + 1);
/* Evaluate the immediate right-hand side */
if (!seta_logShiftTerm(&p, &rval))
@ -473,6 +547,7 @@ seta_bitAndTerm(LPCTSTR* p_, INT* result)
default:
ConErrResPuts(STRING_INVALID_OPERAND);
nErrorLevel = 0x400023CE; // 1073750990;
return FALSE;
}
}
@ -512,17 +587,56 @@ seta_assignment(LPCTSTR* p_, INT* result)
if (identlen)
{
p = skip_ws(p);
/* Handle = assignment */
if (*p == _T('='))
op = *p, p = skip_ws(p + 1);
{
op = *p;
p = skip_ws(p + 1);
}
/* Handle *= /= %= += -= &= ^= |= assignments */
else if (_tcschr(_T("*/%+-&^|"), *p) && p[1] == _T('='))
op = *p, p = skip_ws(p + 2);
else if (_tcschr(_T("*/%+-&^|"), *p))
{
op = *p;
/* Find the '=', there may be some spaces before it */
p = skip_ws(p + 1);
if (*p != _T('='))
{
op = 0;
goto evaluate;
}
/* Skip it */
p = skip_ws(p + 1);
}
/* Handle <<= >>= assignments */
else if (_tcschr(_T("<>"), *p) && *p == p[1] && p[2] == _T('='))
op = *p, p = skip_ws(p + 3);
else if (_tcschr(_T("<>"), *p))
{
op = *p;
/* Check whether the next non-whitespace character is the same operator */
p = skip_ws(p + 1);
if (*p != op)
{
op = 0;
goto evaluate;
}
/* Find the '=', there may be some spaces before it */
p = skip_ws(p + 1);
if (*p != _T('='))
{
op = 0;
goto evaluate;
}
/* Skip it */
p = skip_ws(p + 1);
}
}
evaluate:
/* Allow to chain multiple assignments, such as: a=b=1 */
if (ident && op)
{
@ -613,11 +727,20 @@ seta_eval(LPCTSTR p)
if (!*p)
{
ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT);
nErrorLevel = 1;
return FALSE;
}
if (!seta_stmt(&p, &rval))
return FALSE;
/* If unparsed data remains, fail and bail out */
if (*p)
{
ConErrResPuts(STRING_SYNTAX_COMMAND_INCORRECT); // Actually syntax error / missing operand.
nErrorLevel = 0x400023CE; // 1073750990;
return FALSE;
}
/* Echo the result of the evaluation only in interactive (non-batch) mode */
if (!bc)
ConOutPrintf(_T("%i"), rval);