Number parsing
This commit is contained in:
parent
2385e0e28c
commit
14ea494686
@ -74,6 +74,11 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
|
|||||||
|
|
||||||
case errors::Unexpected_Empty_Source:
|
case errors::Unexpected_Empty_Source:
|
||||||
return os << "unexpected end of input\n";
|
return os << "unexpected end of input\n";
|
||||||
|
|
||||||
|
case errors::Failed_Numeric_Parsing:
|
||||||
|
return err.error_code == std::errc::result_out_of_range
|
||||||
|
? os << "number " << err.message << " cannot be represented with " << (sizeof(Number::num)*8) << " bit number\n"
|
||||||
|
: os << "couldn't parse number " << err.message << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
return os << "unrecognized error type\n";
|
return os << "unrecognized error type\n";
|
||||||
@ -129,6 +134,16 @@ Error errors::unexpected_end_of_source(Location location)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error errors::failed_numeric_parsing(Location location, std::errc errc, std::string_view source)
|
||||||
|
{
|
||||||
|
Error err;
|
||||||
|
err.type = errors::Failed_Numeric_Parsing;
|
||||||
|
err.location = location;
|
||||||
|
err.error_code = errc;
|
||||||
|
err.message = source;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
void errors::all_tokens_were_not_parsed(std::span<Token> tokens)
|
void errors::all_tokens_were_not_parsed(std::span<Token> tokens)
|
||||||
{
|
{
|
||||||
error_heading(std::cerr, std::nullopt, Error_Level::Bug);
|
error_heading(std::cerr, std::nullopt, Error_Level::Bug);
|
||||||
|
@ -43,6 +43,7 @@ namespace errors
|
|||||||
|
|
||||||
Unexpected_Token_Type,
|
Unexpected_Token_Type,
|
||||||
Unexpected_Empty_Source,
|
Unexpected_Empty_Source,
|
||||||
|
Failed_Numeric_Parsing,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ struct Error
|
|||||||
errors::Type type;
|
errors::Type type;
|
||||||
std::optional<Location> location = std::nullopt;
|
std::optional<Location> location = std::nullopt;
|
||||||
std::string message{};
|
std::string message{};
|
||||||
|
std::errc error_code{};
|
||||||
|
|
||||||
bool operator==(errors::Type);
|
bool operator==(errors::Type);
|
||||||
Error with(Location) &&;
|
Error with(Location) &&;
|
||||||
@ -344,9 +346,19 @@ struct Parser
|
|||||||
// Invariant: gcd(num, den) == 1, after any operation
|
// Invariant: gcd(num, den) == 1, after any operation
|
||||||
struct Number
|
struct Number
|
||||||
{
|
{
|
||||||
i64 num = 0, den = 1;
|
using value_type = i64;
|
||||||
|
value_type num = 0, den = 1;
|
||||||
|
|
||||||
auto as_int() const -> i64; // Returns self as int
|
constexpr Number() = default;
|
||||||
|
constexpr Number(Number const&) = default;
|
||||||
|
constexpr Number(Number &&) = default;
|
||||||
|
constexpr Number& operator=(Number const&) = default;
|
||||||
|
constexpr Number& operator=(Number &&) = default;
|
||||||
|
|
||||||
|
explicit Number(value_type v);
|
||||||
|
Number(value_type num, value_type den);
|
||||||
|
|
||||||
|
auto as_int() const -> value_type; // Returns self as int
|
||||||
auto simplify() const -> Number; // Returns self, but with gcd(num, den) == 1
|
auto simplify() const -> Number; // Returns self, but with gcd(num, den) == 1
|
||||||
void simplify_inplace(); // Update self, to have gcd(num, den) == 1
|
void simplify_inplace(); // Update self, to have gcd(num, den) == 1
|
||||||
|
|
||||||
@ -362,6 +374,8 @@ 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);
|
||||||
|
|
||||||
|
static Result<Number> from(Token token);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Number const& num);
|
std::ostream& operator<<(std::ostream& os, Number const& num);
|
||||||
@ -375,6 +389,8 @@ namespace errors
|
|||||||
Error unexpected_token(Token const& unexpected);
|
Error unexpected_token(Token const& unexpected);
|
||||||
Error unexpected_end_of_source(Location location);
|
Error unexpected_end_of_source(Location location);
|
||||||
|
|
||||||
|
Error failed_numeric_parsing(Location location, std::errc errc, std::string_view source);
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
void all_tokens_were_not_parsed(std::span<Token>);
|
void all_tokens_were_not_parsed(std::span<Token>);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
#include <musique.hh>
|
#include <musique.hh>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
Number::Number(value_type v)
|
||||||
|
: num(v), den(1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Number::Number(value_type num, value_type den)
|
||||||
|
: num(num), den(den)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
auto Number::as_int() const -> i64
|
auto Number::as_int() const -> i64
|
||||||
{
|
{
|
||||||
@ -114,3 +125,62 @@ std::ostream& operator<<(std::ostream& os, Number const& n)
|
|||||||
? os << n.num
|
? os << n.num
|
||||||
: os << n.num << '/' << n.den;
|
: os << n.num << '/' << n.den;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr usize Number_Of_Powers = std::numeric_limits<Number::value_type>::digits10 + 1;
|
||||||
|
|
||||||
|
/// Computes compile time array with all possible powers of `multiplier` for given `Number::value_type` value range.
|
||||||
|
static consteval auto compute_powers(Number::value_type multiplier) -> std::array<Number::value_type, Number_Of_Powers>
|
||||||
|
{
|
||||||
|
using V = Number::value_type;
|
||||||
|
|
||||||
|
std::array<V, Number_Of_Powers> powers;
|
||||||
|
powers.front() = 1;
|
||||||
|
std::transform(
|
||||||
|
powers.begin(), std::prev(powers.end()),
|
||||||
|
std::next(powers.begin()),
|
||||||
|
[multiplier](V x) { return x * multiplier; });
|
||||||
|
return powers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `10^n`
|
||||||
|
static Number::value_type pow10(usize n)
|
||||||
|
{
|
||||||
|
static constexpr auto Powers = compute_powers(10);
|
||||||
|
assert(n < Powers.size(), "Trying to compute power of 10 grater then current type can hold");
|
||||||
|
return Powers[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Number> Number::from(Token token)
|
||||||
|
{
|
||||||
|
Number result;
|
||||||
|
|
||||||
|
bool parsed_numerator = false;
|
||||||
|
auto const begin = token.source.data();
|
||||||
|
auto const end = token.source.data() + token.source.size();
|
||||||
|
|
||||||
|
auto [num_end, ec] = std::from_chars(begin, end, result.num);
|
||||||
|
|
||||||
|
if (ec != std::errc{}) {
|
||||||
|
if (ec == std::errc::invalid_argument && begin != end && *begin == '.') {
|
||||||
|
num_end = begin;
|
||||||
|
goto parse_fractional;
|
||||||
|
}
|
||||||
|
return errors::failed_numeric_parsing(std::move(token.location), ec, token.source);
|
||||||
|
}
|
||||||
|
parsed_numerator = true;
|
||||||
|
|
||||||
|
if (num_end != end && *num_end == '.') {
|
||||||
|
parse_fractional:
|
||||||
|
++num_end;
|
||||||
|
decltype(Number::num) frac;
|
||||||
|
auto [frac_end, ec] = std::from_chars(num_end, end, frac);
|
||||||
|
if (ec != std::errc{}) {
|
||||||
|
if (parsed_numerator && ec == std::errc::invalid_argument && token.source != ".")
|
||||||
|
return result;
|
||||||
|
return errors::failed_numeric_parsing(std::move(token.location), ec, token.source);
|
||||||
|
}
|
||||||
|
result += Number{ frac, pow10(frac_end - num_end) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.simplify();
|
||||||
|
}
|
||||||
|
@ -4,6 +4,18 @@
|
|||||||
using namespace boost::ut;
|
using namespace boost::ut;
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
|
void test_number_from(
|
||||||
|
std::string_view source,
|
||||||
|
Number expected,
|
||||||
|
reflection::source_location sl = reflection::source_location::current())
|
||||||
|
{
|
||||||
|
auto result = Number::from(Token { Token::Type::Numeric, source, {} });
|
||||||
|
expect(result.has_value(), sl) << "failed to parse number";
|
||||||
|
if (result.has_value()) {
|
||||||
|
expect(eq(*result, expected), sl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suite number_test = [] {
|
suite number_test = [] {
|
||||||
"Number"_test = [] {
|
"Number"_test = [] {
|
||||||
should("provide arithmetic operators") = [] {
|
should("provide arithmetic operators") = [] {
|
||||||
@ -32,4 +44,12 @@ suite number_test = [] {
|
|||||||
expect(eq(Number{0, 1000}.as_int(), 0)) << "for fraction 0/1000";
|
expect(eq(Number{0, 1000}.as_int(), 0)) << "for fraction 0/1000";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"Number::from"_test = [] {
|
||||||
|
test_number_from("0", Number(0));
|
||||||
|
test_number_from("100", Number(100));
|
||||||
|
test_number_from("0.75", Number(3, 4));
|
||||||
|
test_number_from(".75", Number(3, 4));
|
||||||
|
test_number_from("120.", Number(120, 1));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user