From 14ea49468633ef544f240f212e1e04af7b23c1aa Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Mon, 16 May 2022 00:06:27 +0200 Subject: [PATCH] Number parsing --- src/errors.cc | 15 ++++++++++ src/musique.hh | 24 +++++++++++++--- src/number.cc | 70 +++++++++++++++++++++++++++++++++++++++++++++ src/tests/number.cc | 20 +++++++++++++ 4 files changed, 125 insertions(+), 4 deletions(-) diff --git a/src/errors.cc b/src/errors.cc index 1b8c68c..3b478ad 100644 --- a/src/errors.cc +++ b/src/errors.cc @@ -74,6 +74,11 @@ std::ostream& operator<<(std::ostream& os, Error const& err) case errors::Unexpected_Empty_Source: 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"; @@ -129,6 +134,16 @@ Error errors::unexpected_end_of_source(Location location) 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 tokens) { error_heading(std::cerr, std::nullopt, Error_Level::Bug); diff --git a/src/musique.hh b/src/musique.hh index 96f91cd..bfe3b2a 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -43,6 +43,7 @@ namespace errors Unexpected_Token_Type, Unexpected_Empty_Source, + Failed_Numeric_Parsing, }; } @@ -100,6 +101,7 @@ struct Error errors::Type type; std::optional location = std::nullopt; std::string message{}; + std::errc error_code{}; bool operator==(errors::Type); Error with(Location) &&; @@ -344,11 +346,21 @@ struct Parser // Invariant: gcd(num, den) == 1, after any operation 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 - auto simplify() const -> Number; // Returns self, but with gcd(num, den) == 1 - void simplify_inplace(); // Update self, to have gcd(num, den) == 1 + 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 + void simplify_inplace(); // Update self, to have gcd(num, den) == 1 bool operator==(Number const&) const; bool operator!=(Number const&) const; @@ -362,6 +374,8 @@ struct Number Number& operator*=(Number const& rhs); Number operator/(Number const& rhs) const; Number& operator/=(Number const& rhs); + + static Result from(Token token); }; std::ostream& operator<<(std::ostream& os, Number const& num); @@ -375,6 +389,8 @@ namespace errors Error unexpected_token(Token const& unexpected); Error unexpected_end_of_source(Location location); + Error failed_numeric_parsing(Location location, std::errc errc, std::string_view source); + [[noreturn]] void all_tokens_were_not_parsed(std::span); } diff --git a/src/number.cc b/src/number.cc index 018ef16..f34de21 100644 --- a/src/number.cc +++ b/src/number.cc @@ -1,6 +1,17 @@ #include #include #include +#include + +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 { @@ -114,3 +125,62 @@ std::ostream& operator<<(std::ostream& os, Number const& n) ? os << n.num : os << n.num << '/' << n.den; } + +static constexpr usize Number_Of_Powers = std::numeric_limits::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 +{ + using V = Number::value_type; + + std::array 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::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(); +} diff --git a/src/tests/number.cc b/src/tests/number.cc index c58b1f6..01a58ca 100644 --- a/src/tests/number.cc +++ b/src/tests/number.cc @@ -4,6 +4,18 @@ using namespace boost::ut; 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 = [] { "Number"_test = [] { should("provide arithmetic operators") = [] { @@ -32,4 +44,12 @@ suite number_test = [] { 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)); + }; };