Error reporting improvement for number operations; added power operator
This commit is contained in:
parent
497a4bcc6a
commit
de92564cc1
@ -184,6 +184,16 @@ namespace errors
|
||||
} 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.
|
||||
namespace internal
|
||||
{
|
||||
@ -203,6 +213,7 @@ namespace errors
|
||||
|
||||
/// All possible error types
|
||||
using Details = std::variant<
|
||||
Arithmetic,
|
||||
Closing_Token_Without_Opening,
|
||||
Expected_Expression_Separator_Before,
|
||||
Failed_Numeric_Parsing,
|
||||
@ -456,7 +467,7 @@ struct Token
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@ -703,17 +714,15 @@ struct Number
|
||||
Number& operator-=(Number const& rhs);
|
||||
Number operator*(Number const& rhs) const;
|
||||
Number& operator*=(Number const& rhs);
|
||||
Number operator/(Number const& rhs) const;
|
||||
Number& operator/=(Number const& rhs);
|
||||
Number operator%(Number const& rhs) const;
|
||||
Number& operator%=(Number const& rhs);
|
||||
Result<Number> operator/(Number const& rhs) const;
|
||||
Result<Number> operator%(Number const& rhs) const;
|
||||
|
||||
Number floor() const; ///< Return number rounded down to nearest integer
|
||||
Number ceil() const; ///< Return number rounded up to nearest integer
|
||||
Number round() const; ///< Return number rounded to nearest integer
|
||||
|
||||
Number inverse() const; ///< Return number raised to power -1
|
||||
Number pow(Number n) const; ///< Return number raised to power `n`.
|
||||
Result<Number> inverse() const; ///< Return number raised to power -1
|
||||
Result<Number> pow(Number n) const; ///< Return number raised to power `n`.
|
||||
|
||||
/// Parses source contained by token into a Number instance
|
||||
static Result<Number> from(Token token);
|
||||
|
@ -87,7 +87,6 @@ static Result<Value> plus_minus_operator(Interpreter &interpreter, std::vector<V
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
template<typename Binary_Operation, char ...Chars>
|
||||
static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value> args)
|
||||
{
|
||||
@ -97,8 +96,12 @@ static Result<Value> binary_operator(Interpreter& interpreter, std::vector<Value
|
||||
|
||||
if (NN::typecheck(args)) {
|
||||
auto [lhs, rhs] = NN::move_from(args);
|
||||
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)) {
|
||||
return vectorize(binary_operator<Binary_Operation, Chars...>, interpreter, args);
|
||||
@ -182,6 +185,14 @@ static Result<Value> multiplication_operator(Interpreter &i, std::vector<Value>
|
||||
|
||||
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
|
||||
static constexpr auto Operators = std::array {
|
||||
Operator_Entry { "+", plus_minus_operator<std::plus<>> },
|
||||
@ -189,6 +200,7 @@ static constexpr auto Operators = std::array {
|
||||
Operator_Entry { "*", multiplication_operator },
|
||||
Operator_Entry { "/", binary_operator<std::divides<>, '/'> },
|
||||
Operator_Entry { "%", binary_operator<std::modulus<>, '%'> },
|
||||
Operator_Entry { "**", binary_operator<pow_operator, '*', '*'> },
|
||||
|
||||
Operator_Entry { "<", comparison_operator<std::less<>> },
|
||||
Operator_Entry { ">", comparison_operator<std::greater<>> },
|
||||
|
@ -146,6 +146,15 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
|
||||
[](errors::Expected_Expression_Separator_Before const&) { return "Missing semicolon"; },
|
||||
[](errors::Literal_As_Identifier const&) { return "Literal used in place of an identifier"; },
|
||||
[](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) {
|
||||
return err.type == errors::Closing_Token_Without_Opening::Block
|
||||
? "Block closing without opening"
|
||||
@ -368,6 +377,32 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
|
||||
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(); },
|
||||
}, err.details);
|
||||
|
||||
|
101
src/number.cc
101
src/number.cc
@ -106,37 +106,72 @@ Number& Number::operator*=(Number const& rhs)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number Number::operator/(Number const& rhs) const
|
||||
Result<Number> Number::operator/(Number const& rhs) const
|
||||
{
|
||||
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;
|
||||
den *= rhs.num;
|
||||
simplify_inplace();
|
||||
return *this;
|
||||
}
|
||||
using N = Number::value_type;
|
||||
N t = 0, newt = 1;
|
||||
N r = n, newr = a;
|
||||
|
||||
|
||||
Number Number::operator%(Number const& rhs) const
|
||||
{
|
||||
return Number(*this) %= rhs;
|
||||
}
|
||||
|
||||
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");
|
||||
while (newr != 0) {
|
||||
auto q = r / newr;
|
||||
t = std::exchange(newt, t - q * newt);
|
||||
r = std::exchange(newr, r - q * newr);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -252,16 +287,22 @@ Number Number::round() const
|
||||
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 };
|
||||
}
|
||||
|
||||
namespace impl
|
||||
{
|
||||
// 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
|
||||
// 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.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();
|
||||
|
||||
@ -289,9 +330,5 @@ Number Number::pow(Number n) const
|
||||
// Hard case, we raise this to fractional power.
|
||||
// Essentialy finding n.den root of (x to n.num power).
|
||||
// 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();
|
||||
}
|
||||
|
@ -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
|
||||
//
|
||||
// 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, "and")) return 150;
|
||||
if (one_of(op, "<", ">", "<=", ">=", "==", "!=")) return 200;
|
||||
if (one_of(op, "+", "-")) return 300;
|
||||
if (one_of(op, "*", "/", "%", "&")) return 400;
|
||||
if (one_of(op, "**")) return 500;
|
||||
|
||||
unreachable();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user