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;
|
} 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);
|
||||||
|
@ -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,8 +96,12 @@ 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);
|
||||||
|
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));
|
return Value::from(Binary_Operation{}(lhs, rhs));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (may_be_vectorized(args)) {
|
if (may_be_vectorized(args)) {
|
||||||
return vectorize(binary_operator<Binary_Operation, Chars...>, interpreter, 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>;
|
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<>> },
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
if (rhs.num == 0) {
|
||||||
|
return Error {
|
||||||
|
.details = errors::Arithmetic {
|
||||||
|
.type = errors::Arithmetic::Division_By_Zero
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
return Number{num * rhs.den, den * rhs.num}.simplify();
|
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) {
|
||||||
|
auto q = r / newr;
|
||||||
|
t = std::exchange(newt, t - q * newt);
|
||||||
|
r = std::exchange(newr, r - q * newr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r > 1) {
|
||||||
|
return Error {
|
||||||
|
.details = errors::Arithmetic {
|
||||||
|
.type = errors::Arithmetic::Unable_To_Calculate_Modular_Multiplicative_Inverse
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return t < 0 ? t + n : t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Number Number::operator%(Number const& rhs) const
|
Result<Number> Number::operator%(Number const& rhs) const
|
||||||
{
|
{
|
||||||
return Number(*this) %= rhs;
|
if (rhs.num == 0) {
|
||||||
|
return Error {
|
||||||
|
.details = errors::Arithmetic {
|
||||||
|
.type = errors::Arithmetic::Division_By_Zero
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Number& Number::operator%=(Number const& rhs)
|
auto ret = simplify();
|
||||||
{
|
|
||||||
simplify_inplace();
|
|
||||||
auto [rnum, rden] = rhs.simplify();
|
auto [rnum, rden] = rhs.simplify();
|
||||||
|
|
||||||
if (den == 1 && rden == 1) {
|
if (rden != 1) {
|
||||||
num %= rnum;
|
return Error {
|
||||||
} else {
|
.details = errors::Arithmetic {
|
||||||
assert(false, "Modulo for fractions is not supported");
|
.type = errors::Arithmetic::Fractional_Modulo
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user