math: Give a proper error for division by zero

This errored out *later* because the result was infinite or NaN, but
it didn't actually stop evaluation.

I'm not sure if there is a way to get floating point math to turn an
infinity back into something that doesn't depend on a literal
infinity, but division by zero conceptually isn't a thing we can
support.

There's entire branches of maths dedicated to figuring out what
dividing by "basically zero" means and we don't have to get into it.
This commit is contained in:
Fabian Boehm 2022-09-08 16:50:18 +02:00
parent 7f1e9bf57f
commit 5edba044a3
4 changed files with 29 additions and 7 deletions

View File

@ -177,6 +177,8 @@ static const wchar_t *math_describe_error(const te_error_t &error) {
return _(L"Unexpected token"); return _(L"Unexpected token");
case TE_ERROR_LOGICAL_OPERATOR: case TE_ERROR_LOGICAL_OPERATOR:
return _(L"Logical operations are not supported, use `test` instead"); return _(L"Logical operations are not supported, use `test` instead");
case TE_ERROR_DIV_BY_ZERO:
return _(L"Division by zero");
case TE_ERROR_UNKNOWN: case TE_ERROR_UNKNOWN:
return _(L"Expression is bogus"); return _(L"Expression is bogus");
default: default:

View File

@ -114,7 +114,10 @@ struct state {
[[nodiscard]] te_error_t error() const { [[nodiscard]] te_error_t error() const {
if (type_ == TOK_END) return {TE_ERROR_NONE, 0}; if (type_ == TOK_END) return {TE_ERROR_NONE, 0};
te_error_t err{error_, static_cast<int>(next_ - start_) + 1}; // If we have an error position set, use that,
// otherwise the current position.
const wchar_t *tok = errpos_ ? errpos_ : next_;
te_error_t err{error_, static_cast<int>(tok - start_) + 1};
if (error_ == TE_ERROR_NONE) { if (error_ == TE_ERROR_NONE) {
// If we're not at the end but there's no error, then that means we have a // If we're not at the end but there's no error, then that means we have a
// superfluous token that we have no idea what to do with. // superfluous token that we have no idea what to do with.
@ -129,6 +132,7 @@ struct state {
const wchar_t *start_; const wchar_t *start_;
const wchar_t *next_; const wchar_t *next_;
const wchar_t *errpos_{nullptr};
te_fun_t current_{NAN}; te_fun_t current_{NAN};
void next_token(); void next_token();
@ -520,8 +524,17 @@ double state::term() {
auto ret = factor(); auto ret = factor();
while (type_ == TOK_INFIX && (current_ == mul || current_ == divide || current_ == fmod)) { while (type_ == TOK_INFIX && (current_ == mul || current_ == divide || current_ == fmod)) {
auto fn = current_; auto fn = current_;
auto tok = next_;
next_token(); next_token();
ret = fn(ret, factor()); auto ret2 = factor();
if (ret2 == 0 && (fn == divide || fn == fmod)) {
// Division by zero (also for modulo)
type_ = TOK_ERROR;
error_ = TE_ERROR_DIV_BY_ZERO;
// Error position is the "/" or "%" sign for now
errpos_ = tok;
}
ret = fn(ret, ret2);
} }
return ret; return ret;
} }

View File

@ -37,7 +37,8 @@ typedef enum {
TE_ERROR_MISSING_OPERATOR = 6, TE_ERROR_MISSING_OPERATOR = 6,
TE_ERROR_UNEXPECTED_TOKEN = 7, TE_ERROR_UNEXPECTED_TOKEN = 7,
TE_ERROR_LOGICAL_OPERATOR = 8, TE_ERROR_LOGICAL_OPERATOR = 8,
TE_ERROR_UNKNOWN = 9 TE_ERROR_DIV_BY_ZERO = 9,
TE_ERROR_UNKNOWN = 10
} te_error_type_t; } te_error_type_t;
typedef struct te_error_t { typedef struct te_error_t {

View File

@ -151,9 +151,14 @@ not math -s 12
not math 2^999999 not math 2^999999
# CHECKERR: math: Error: Result is infinite # CHECKERR: math: Error: Result is infinite
# CHECKERR: '2^999999' # CHECKERR: '2^999999'
not math 1 / 0 printf '<%s>\n' (not math 1 / 0 2>&1)
# CHECKERR: math: Error: Result is infinite # CHECK: <math: Error: Division by zero>
# CHECKERR: '1 / 0' # CHECK: <'1 / 0'>
# CHECK: < ^>
printf '<%s>\n' (math 1 % 0 - 5 2>&1)
# CHECK: <math: Error: Division by zero>
# CHECK: <'1 % 0 - 5'>
# CHECK: < ^>
# Validate "x" as multiplier # Validate "x" as multiplier
math 0x2 # Hex math 0x2 # Hex
@ -259,8 +264,9 @@ math pow 2 x cos'(-pi)', 2
# This used to take ages, see #8170. # This used to take ages, see #8170.
# If this test hangs, that's reintroduced! # If this test hangs, that's reintroduced!
math 'ncr(0/0, 1)' math 'ncr(0/0, 1)'
# CHECKERR: math: Error: Result is infinite # CHECKERR: math: Error: Division by zero
# CHECKERR: 'ncr(0/0, 1)' # CHECKERR: 'ncr(0/0, 1)'
# CHECKERR: ^
# Variadic functions require at least one argument # Variadic functions require at least one argument
math min math min