Skip to content

Commit

Permalink
Add decimal_literal parser, add char_parser, convert
Browse files Browse the repository at this point in the history
- move char_parser to own header
- add decimal_literal parser
- adder basic convert functions
- Hit Clang's [reference to local binding fails in enclosing function #48582](
  llvm/llvm-project#48582)
- cosmetic printing
- using constexpr LUT for exp base 10
  • Loading branch information
Olaf committed Jul 7, 2022
1 parent 13fe12f commit 5259f13
Show file tree
Hide file tree
Showing 8 changed files with 499 additions and 53 deletions.
22 changes: 21 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@
"xlocmes": "cpp",
"xtree": "cpp",
"span": "cpp",
"charconv": "cpp"
"charconv": "cpp",
"chrono": "cpp",
"csignal": "cpp",
"format": "cpp",
"forward_list": "cpp",
"fstream": "cpp",
"mutex": "cpp",
"numeric": "cpp",
"random": "cpp",
"ratio": "cpp",
"stop_token": "cpp",
"strstream": "cpp",
"thread": "cpp",
"*.tcc": "cpp",
"condition_variable": "cpp",
"memory_resource": "cpp",
"string_view": "cpp",
"numbers": "cpp",
"semaphore": "cpp",
"cfenv": "cpp",
"cinttypes": "cpp"
}
}
19 changes: 15 additions & 4 deletions source/literal/include/literal/ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <string>
#include <string_view>
#include <optional>
#include <variant>
#include <vector>

namespace ast {
Expand All @@ -28,18 +29,25 @@ struct real_type : x3::position_tagged {
// boost::iterator_range<Iter> later on
std::string integer; // base: 2,8,10,16
std::string fractional; // base: 2,8,10,16
optional<std::int32_t> exponent;
std::string exponent;
// numeric representation
using value_type = double;
std::optional<value_type> value;
};

struct integer_type : x3::position_tagged {
std::string integer; // base: 2,8,10,16
optional<std::uint32_t> exponent; // positive only!
std::string integer; // base: 2,8,10,16
std::string exponent; // positive only!
// numeric representation
using value_type = std::uint32_t;
std::optional<value_type> value;
};

struct based_literal : x3::position_tagged {
using num_type = variant<real_type, integer_type>;
std::uint32_t base;
num_type num;
// numeric representation
// e.g. https://coliru.stacked-crooked.com/a/652a879e37b4ea37
// std::variant<RealT, IntT> const value() // lazy numeric conversion
};
Expand All @@ -48,14 +56,17 @@ struct decimal_literal : x3::position_tagged {
using num_type = variant<real_type, integer_type>;
std::uint32_t base;
num_type num;
// std::variant<RealT, IntT> const value() // lazy numeric conversion
// numeric representation
// using value_type = std::variant<std::monostate, double, std::uint32_t>;
// value_type value;
};

// Note: The literal representation is needed, at the latest with VHDL 2008
// where also literals like 12UX"F-" are possible.
struct bit_string_literal : x3::position_tagged {
std::uint32_t base;
std::string literal; // base: 2,8,10,16
// numeric representation
using value_type = std::uint32_t;
std::optional<value_type> value;
};
Expand Down
157 changes: 157 additions & 0 deletions source/literal/include/literal/convert.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//
// Copyright (c) 2017-2022 Olaf (<[email protected]>).
// SPDX-License-Identifier: GPL-3.0-or-later
//

#pragma once

#include <range/v3/view/filter.hpp>
#include <range/v3/range/conversion.hpp>
#include <charconv>

#include <string>
#include <string_view>

#include <iostream>
#include <iomanip>

namespace convert {

namespace detail {

// prune the literal and copy result to string
static inline std::string prune(auto literal)
{
namespace views = ranges::views;

return ranges::to<std::string>(literal | views::filter([](char chr) { return chr != '_'; }));
}

template <typename TargetT>
static inline auto as_unsigned(std::string_view literal, unsigned base, std::error_code& ec)
{
static_assert(std::is_integral_v<TargetT> && std::is_unsigned_v<TargetT>,
"TargetT must be of unsigned integral type");

// std::from_chars() works with raw char pointers
char const* const end = literal.data() + literal.size();

TargetT result = static_cast<TargetT>(-1);
auto const [ptr, errc] = std::from_chars(literal.data(), end, result, base);

#if !defined(__clang__)
// [reference to local binding fails in enclosing function #48582](
// https://github.com/llvm/llvm-project/issues/48582)
auto const get_error_code = [&]() {
if (ptr != end) {
// something of input is wrong, outer parser fails
return std::make_error_code(std::errc::invalid_argument);
}
if (errc != std::errc{}) {
// maybe errc::result_out_of_range, errc::invalid_argument ...
return std::make_error_code(errc);
}
return std::error_code{};
};

ec = get_error_code();
#else
#warning Using Clang work-around
if (ptr != end) {
// something of input is wrong, outer parser fails
ec = std::make_error_code(std::errc::invalid_argument);
}
if (errc != std::errc{}) {
// maybe errc::result_out_of_range, errc::invalid_argument ...
ec = std::make_error_code(errc);
}
ec = std::error_code{};
#endif

return result;
}

template <typename TargetT>
static inline auto as_real(std::string_view literal, unsigned base, std::error_code& ec)
{
static_assert(std::is_floating_point_v<TargetT>, "TargetT must be of floating point type");

assert(base == 10U && "only base 10 is supported");

// std::from_chars() works with raw char pointers
const char* const end = literal.data() + literal.size();

TargetT result; // = std::numeric_limits<TargetT>::quiet_NaN();
auto const [ptr, errc] =
std::from_chars(literal.data(), end, result, std::chars_format::general);

#if !defined(__clang__)
// [reference to local binding fails in enclosing function #48582](
// https://github.com/llvm/llvm-project/issues/48582)
auto const get_error_code = [&]() {
if (ptr != end) {
// something of input is wrong, outer parser fails
return std::make_error_code(std::errc::invalid_argument);
}
if (errc != std::errc{}) {
// maybe errc::result_out_of_range, errc::invalid_argument ...
return std::make_error_code(errc);
}
return std::error_code{};
};

ec = get_error_code();
#else
#warning Using Clang work-around
if (ptr != end) {
// something of input is wrong, outer parser fails
ec = std::make_error_code(std::errc::invalid_argument);
}
if (errc != std::errc{}) {
// maybe errc::result_out_of_range, errc::invalid_argument ...
ec = std::make_error_code(errc);
}
ec = std::error_code{};
#endif

return result;
}

// concept, see [coliru](https://coliru.stacked-crooked.com/a/ee869b77c78b496d)
template <typename T, unsigned Base>
class exp_scale {
static_assert(std::is_integral_v<T>, "T must be integral type");

public:
using value_type = T;

public:
constexpr exp_scale()
{
assert(Base == 10U && "This table was developed only with Base 10 in mind!");

T value = 1;
for (std::size_t i = 0; i != SIZE; ++i) {
data[i] = value;
value *= Base;
}
}

T operator[](unsigned idx) const
{
assert(idx < SIZE && "exponent index out of range");
return data[idx];
}

unsigned max_index() const { return SIZE; }

private:
static constexpr auto SIZE = std::numeric_limits<T>::digits10 + 1;
std::array<value_type, SIZE> data;
};

template <typename T>
static constexpr exp_scale<T, 10U> exp10_scale = {};

} // namespace detail
} // namespace convert
34 changes: 18 additions & 16 deletions source/literal/include/literal/parser/bit_string_literal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <literal/ast.hpp>
#include <literal/parser/literal_base_parser.hpp>
#include <literal/parser/char_parser.hpp>

#include <boost/spirit/home/x3.hpp>

Expand Down Expand Up @@ -78,20 +79,6 @@ static const do_lazy_type<T> do_lazy{};

} // namespace detail

namespace char_parser {

auto const delimit_numeric_digits = [](auto&& char_range, char const* name) {
auto cs = x3::char_(char_range);
return detail::as<std::string>(x3::raw[cs >> *('_' >> +cs | cs)], name);
};

auto const bin_digits = delimit_numeric_digits("01", "binary digits");
auto const oct_digits = delimit_numeric_digits("0-7", "octal digits");
auto const dec_digits = delimit_numeric_digits("0-9", "decimal digits");
auto const hex_digits = delimit_numeric_digits("0-9a-fA-F", "hexadecimal digits");

} // namespace char_parser

// BNF: bit_string_literal ::= base_specifier " [ bit_value ] "
struct bit_string_literal : x3::parser<bit_string_literal> {
using attribute_type = ast::bit_string_literal;
Expand Down Expand Up @@ -123,6 +110,8 @@ struct bit_string_literal : x3::parser<bit_string_literal> {
auto const base_specifier = x3::rule<struct _, std::uint32_t>{ "base specifier" } =
x3::no_case[ base_id ];

// [Boost spirit x3 - lazy parser with compile time known parsers, referring to a previously matched value](
// https://stackoverflow.com/questions/72833517/boost-spirit-x3-lazy-parser-with-compile-time-known-parsers-referring-to-a-pr)
auto const grammar = x3::rule<struct _, attribute_type, true>{ "bit string literal" } =
x3::with<rule_type>( rule_type{} )[
x3::lexeme[
Expand Down Expand Up @@ -151,7 +140,7 @@ struct bit_string_literal : x3::parser<bit_string_literal> {
{ "x", 16 },
}, "base id");
// clang-format on

private:
template <typename TargetT>
std::optional<TargetT> convert(auto range, unsigned base) const
Expand All @@ -168,6 +157,8 @@ struct bit_string_literal : x3::parser<bit_string_literal> {
TargetT result;
auto const [ptr, errc] = std::from_chars(pruned.data(), end, result, base);

// FixMe: move extra header, see convert.hpp
#if !defined(__clang__)
auto const ec = [=]() {
if (ptr != end) {
// something of input is wrong, outer parser fails
Expand All @@ -179,7 +170,18 @@ struct bit_string_literal : x3::parser<bit_string_literal> {
}
return std::error_code{};
}();

#else
std::error_code ec;
if (ptr != end) {
// something of input is wrong, outer parser fails
ec = std::make_error_code(std::errc::invalid_argument);
}
if (errc != std::errc{}) {
// maybe errc::result_out_of_range, errc::invalid_argument ...
ec = std::make_error_code(errc);
}
ec = std::error_code{};
#endif
if (ec) {
std::cerr << "bit_string_literal error: " << ec.message() << '\n';
return {};
Expand Down
32 changes: 32 additions & 0 deletions source/literal/include/literal/parser/char_parser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// Copyright (c) 2017-2022 Olaf (<[email protected]>).
// SPDX-License-Identifier: GPL-3.0-or-later
//

#pragma once

#include <boost/spirit/home/x3.hpp>

#include <string>

namespace parser {

namespace x3 = boost::spirit::x3;

namespace char_parser {

auto const delimit_numeric_digits = [](auto&& char_range, char const* name) {
auto cs = x3::char_(char_range);
// clang-format off
return x3::rule<struct _, std::string>{ "numeric digits" } =
x3::raw[cs >> *('_' >> +cs | cs)];
// clang-format on
};

auto const bin_digits = delimit_numeric_digits("01", "binary digits");
auto const oct_digits = delimit_numeric_digits("0-7", "octal digits");
auto const dec_digits = delimit_numeric_digits("0-9", "decimal digits");
auto const hex_digits = delimit_numeric_digits("0-9a-fA-F", "hexadecimal digits");

} // namespace char_parser
} // namespace parser
Loading

0 comments on commit 5259f13

Please sign in to comment.