Error reporting improvement for number operations; added power operator

This commit is contained in:
Robert Bendun 2022-09-04 19:44:33 +02:00
parent 497a4bcc6a
commit de92564cc1
5 changed files with 137 additions and 43 deletions

View File

@ -184,6 +184,16 @@ namespace errors
} type; } type;
}; };
struct Arithmetic
{
enum Type
{
Division_By_Zero,
Fractional_Modulo,
Unable_To_Calculate_Modular_Multiplicative_Inverse
} type;
};
/// Collection of messages that are considered internal and should not be printed to the end user. /// Collection of messages that are considered internal and should not be printed to the end user.
namespace internal namespace internal
{ {
@ -203,6 +213,7 @@ namespace errors
/// All possible error types /// All possible error types
using Details = std::variant< using Details = std::variant<
Arithmetic,
Closing_Token_Without_Opening, Closing_Token_Without_Opening,
Expected_Expression_Separator_Before, Expected_Expression_Separator_Before,
Failed_Numeric_Parsing, Failed_Numeric_Parsing,
@ -456,7 +467,7 @@ struct Token
}; };
static constexpr usize Keywords_Count = 6; static constexpr usize Keywords_Count = 6;
static constexpr usize Operators_Count = 15; static constexpr usize Operators_Count = 16;
std::string_view type_name(Token::Type type); std::string_view type_name(Token::Type type);
@ -703,17 +714,15 @@ struct Number
Number& operator-=(Number const& rhs); Number& operator-=(Number const& rhs);
Number operator*(Number const& rhs) const; Number operator*(Number const& rhs) const;
Number& operator*=(Number const& rhs); Number& operator*=(Number const& rhs);
Number operator/(Number const& rhs) const; Result<Number> operator/(Number const& rhs) const;
Number& operator/=(Number const& rhs); Result<Number> operator%(Number const& rhs) const;
Number operator%(Number const& rhs) const;
Number& operator%=(Number const& rhs);
Number floor() const; ///< Return number rounded down to nearest integer Number floor() const; ///< Return number rounded down to nearest integer
Number ceil() const; ///< Return number rounded up to nearest integer Number ceil() const; ///< Return number rounded up to nearest integer
Number round() const; ///< Return number rounded to nearest integer Number round() const; ///< Return number rounded to nearest integer
Number inverse() const; ///< Return number raised to power -1 Result<Number> inverse() const; ///< Return number raised to power -1
Number pow(Number n) const; ///< Return number raised to power `n`. Result<Number> pow(Number n) const; ///< Return number raised to power `n`.
/// Parses source contained by token into a Number instance /// Parses source contained by token into a Number instance
static Result<Number> from(Token token); static Result<Number> from(Token token);

View File

@ -87,7 +87,6 @@ static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<V
}; };
} }
template<typename Binary_Operation, char ...Chars> template<typename Binary_Operation, char ...Chars>
static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value> args) static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value> args)
{ {
@ -97,7 +96,11 @@ static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value
if (NN::typecheck(args)) { if (NN::typecheck(args)) {
auto [lhs, rhs] = NN::move_from(args); auto [lhs, rhs] = NN::move_from(args);
return Value::from(Binary_Operation{}(lhs, rhs)); if constexpr (is_template_v<Result, decltype(Binary_Operation{}(lhs, rhs))>) {
return Value::from(Try(Binary_Operation{}(lhs, rhs)));
} else {
return Value::from(Binary_Operation{}(lhs, rhs));
}
} }
if (may_be_vectorized(args)) { if (may_be_vectorized(args)) {
@ -182,6 +185,14 @@ static Result<Value> multiplication_operator(Interpreter &i, std::vector<Value>
using Operator_Entry = std::tuple<char const*, Intrinsic>; using Operator_Entry = std::tuple<char const*, Intrinsic>;
struct pow_operator
{
inline Result<Number> operator()(Number lhs, Number rhs)
{
return lhs.pow(rhs);
}
};
/// Operators definition table /// Operators definition table
static constexpr auto Operators = std::array { static constexpr auto Operators = std::array {
Operator_Entry { "+", plus_minus_operator<std::plus<>> }, Operator_Entry { "+", plus_minus_operator<std::plus<>> },
@ -189,6 +200,7 @@ static constexpr auto Operators = std::array {
Operator_Entry { "*", multiplication_operator }, Operator_Entry { "*", multiplication_operator },
Operator_Entry { "/", binary_operator<std::divides<>, '/'> }, Operator_Entry { "/", binary_operator<std::divides<>, '/'> },
Operator_Entry { "%", binary_operator<std::modulus<>, '%'> }, Operator_Entry { "%", binary_operator<std::modulus<>, '%'> },
Operator_Entry { "**", binary_operator<pow_operator, '*', '*'> },
Operator_Entry { "<", comparison_operator<std::less<>> }, Operator_Entry { "<", comparison_operator<std::less<>> },
Operator_Entry { ">", comparison_operator<std::greater<>> }, Operator_Entry { ">", comparison_operator<std::greater<>> },

View File

@ -146,6 +146,15 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
[](errors::Expected_Expression_Separator_Before const&) { return "Missing semicolon"; }, [](errors::Expected_Expression_Separator_Before const&) { return "Missing semicolon"; },
[](errors::Literal_As_Identifier const&) { return "Literal used in place of an identifier"; }, [](errors::Literal_As_Identifier const&) { return "Literal used in place of an identifier"; },
[](errors::Out_Of_Range const&) { return "Index out of range"; }, [](errors::Out_Of_Range const&) { return "Index out of range"; },
[](errors::Arithmetic const& err) {
switch (err.type) {
case errors::Arithmetic::Division_By_Zero: return "Division by 0";
case errors::Arithmetic::Fractional_Modulo: return "Modulo with fractions";
case errors::Arithmetic::Unable_To_Calculate_Modular_Multiplicative_Inverse:
return "Missing modular inverse";
default: unreachable();
}
},
[](errors::Closing_Token_Without_Opening const& err) { [](errors::Closing_Token_Without_Opening const& err) {
return err.type == errors::Closing_Token_Without_Opening::Block return err.type == errors::Closing_Token_Without_Opening::Block
? "Block closing without opening" ? "Block closing without opening"
@ -368,6 +377,32 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
print_error_line(loc); print_error_line(loc);
}, },
[&](errors::Arithmetic const& err) {
switch (err.type) {
break; case errors::Arithmetic::Division_By_Zero:
os << "Tried to divide by 0 which is undefined operation in math\n";
os << "\n";
print_error_line(loc);
break; case errors::Arithmetic::Fractional_Modulo:
os << "Tried to calculate modulo with fractional modulus which is not defined\n";
os << "\n";
print_error_line(loc);
os << pretty::begin_comment;
os << "Example code that could raise this error:\n";
os << " 1 % (1/2)\n";
os << pretty::end;
break; case errors::Arithmetic::Unable_To_Calculate_Modular_Multiplicative_Inverse:
os << "Tried to calculate fraction in modular space.\n";
os << "\n";
print_error_line(loc);
}
},
[&](errors::Unexpected_Keyword const&) { unimplemented(); }, [&](errors::Unexpected_Keyword const&) { unimplemented(); },
}, err.details); }, err.details);

View File

@ -106,37 +106,72 @@ Number& Number::operator*=(Number const& rhs)
return *this; return *this;
} }
Number Number::operator/(Number const& rhs) const Result<Number> Number::operator/(Number const& rhs) const
{ {
return Number{num * rhs.den, den * rhs.num}.simplify(); if (rhs.num == 0) {
return Error {
.details = errors::Arithmetic {
.type = errors::Arithmetic::Division_By_Zero
}
};
}
return Number{num * rhs.den, den * rhs.num}.simplify();
} }
Number& Number::operator/=(Number const& rhs) inline auto modular_inverse(Number::value_type a, Number::value_type n)
-> Result<Number::value_type>
{ {
num *= rhs.den; using N = Number::value_type;
den *= rhs.num; N t = 0, newt = 1;
simplify_inplace(); N r = n, newr = a;
return *this;
}
while (newr != 0) {
Number Number::operator%(Number const& rhs) const auto q = r / newr;
{ t = std::exchange(newt, t - q * newt);
return Number(*this) %= rhs; r = std::exchange(newr, r - q * newr);
}
Number& Number::operator%=(Number const& rhs)
{
simplify_inplace();
auto [rnum, rden] = rhs.simplify();
if (den == 1 && rden == 1) {
num %= rnum;
} else {
assert(false, "Modulo for fractions is not supported");
} }
return *this; if (r > 1) {
return Error {
.details = errors::Arithmetic {
.type = errors::Arithmetic::Unable_To_Calculate_Modular_Multiplicative_Inverse
}
};
}
return t < 0 ? t + n : t;
}
Result<Number> Number::operator%(Number const& rhs) const
{
if (rhs.num == 0) {
return Error {
.details = errors::Arithmetic {
.type = errors::Arithmetic::Division_By_Zero
}
};
}
auto ret = simplify();
auto [rnum, rden] = rhs.simplify();
if (rden != 1) {
return Error {
.details = errors::Arithmetic {
.type = errors::Arithmetic::Fractional_Modulo
}
};
}
if (ret.den == 1) {
ret.num %= rnum;
return ret;
}
return Number(
(Try(modular_inverse(ret.den, rnum)) * ret.num) % rnum
);
} }
std::ostream& operator<<(std::ostream& os, Number const& n) std::ostream& operator<<(std::ostream& os, Number const& n)
@ -252,16 +287,22 @@ Number Number::round() const
return impl::round(*this, impl::Rounding_Mode::Round); return impl::round(*this, impl::Rounding_Mode::Round);
} }
Number Number::inverse() const Result<Number> Number::inverse() const
{ {
assert(num != 0, "Cannot take inverse of fraction with 0 in denominator"); if (num == 0) {
return Error {
.details = errors::Arithmetic {
.type = errors::Arithmetic::Division_By_Zero
}
};
}
return { den, num }; return { den, num };
} }
namespace impl namespace impl
{ {
// Raise Number to integer power helper // Raise Number to integer power helper
static inline Number pow(Number const& x, decltype(Number::num) n) static inline Result<Number> pow(Number const& x, decltype(Number::num) n)
{ {
// We simply raise numerator and denominator to required power // We simply raise numerator and denominator to required power
// and if n is negative we take inverse. // and if n is negative we take inverse.
@ -273,11 +314,11 @@ namespace impl
for (auto i = n; i != 0; --i) result.num *= x.num; for (auto i = n; i != 0; --i) result.num *= x.num;
for (auto i = n; i != 0; --i) result.den *= x.den; for (auto i = n; i != 0; --i) result.den *= x.den;
return flip ? result.inverse() : result; return flip ? Try(result.inverse()) : result;
} }
} }
Number Number::pow(Number n) const Result<Number> Number::pow(Number n) const
{ {
n.simplify_inplace(); n.simplify_inplace();
@ -289,9 +330,5 @@ Number Number::pow(Number n) const
// Hard case, we raise this to fractional power. // Hard case, we raise this to fractional power.
// Essentialy finding n.den root of (x to n.num power). // Essentialy finding n.den root of (x to n.num power).
// We need to protect ourselfs against even roots of negative numbers. // We need to protect ourselfs against even roots of negative numbers.
// TODO Implement this case properly.
//
// TODO(assert) <- taking power is not always doable so this operation
// should produce error not crash with assertion failure in the future.
unimplemented(); unimplemented();
} }

View File

@ -519,13 +519,14 @@ static usize precedense(std::string_view op)
// '.' since it have own precedense rules and is not binary expression but its own kind of expression // '.' since it have own precedense rules and is not binary expression but its own kind of expression
// //
// Exclusion of them is marked by subtracting total number of excluded operators. // Exclusion of them is marked by subtracting total number of excluded operators.
static_assert(Operators_Count - 1 == 14, "Ensure that all operators have defined precedense below"); static_assert(Operators_Count - 1 == 15, "Ensure that all operators have defined precedense below");
if (one_of(op, "or")) return 100; if (one_of(op, "or")) return 100;
if (one_of(op, "and")) return 150; if (one_of(op, "and")) return 150;
if (one_of(op, "<", ">", "<=", ">=", "==", "!=")) return 200; if (one_of(op, "<", ">", "<=", ">=", "==", "!=")) return 200;
if (one_of(op, "+", "-")) return 300; if (one_of(op, "+", "-")) return 300;
if (one_of(op, "*", "/", "%", "&")) return 400; if (one_of(op, "*", "/", "%", "&")) return 400;
if (one_of(op, "**")) return 500;
unreachable(); unreachable();
} }