Skip to content

Commit

Permalink
feat: dimension text output added
Browse files Browse the repository at this point in the history
Resolves #421
  • Loading branch information
mpusz committed Feb 26, 2024
1 parent 10ee4cc commit e50d75a
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 94 deletions.
76 changes: 74 additions & 2 deletions src/core/include/mp-units/bits/text_tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
#pragma once

#include <mp-units/bits/external/fixed_string.h>
#include <mp-units/bits/ratio.h>
#include <mp-units/bits/symbol_text.h>

namespace mp_units::detail {
namespace mp_units {

namespace detail {

template<std::intmax_t Value>
requires(0 <= Value) && (Value < 10)
Expand Down Expand Up @@ -84,4 +87,73 @@ template<std::intmax_t Value>
return regular<Value / 10>() + regular<Value % 10>();
}

} // namespace mp_units::detail
} // namespace detail

enum class text_encoding : std::int8_t {
unicode, // m³; µs
ascii, // m^3; us
default_encoding = unicode
};

namespace detail {

template<typename CharT, std::size_t N, std::size_t M, std::output_iterator<CharT> Out>
constexpr Out copy(const basic_symbol_text<N, M>& txt, text_encoding encoding, Out out)
{
if (encoding == text_encoding::unicode) {
if constexpr (is_same_v<CharT, char8_t>)
return copy(txt.unicode(), out).out;
else if constexpr (is_same_v<CharT, char>) {
for (char8_t ch : txt.unicode()) *out++ = static_cast<char>(ch);
return out;
} else
throw std::invalid_argument("Unicode text can't be copied to CharT output");
} else {
if constexpr (is_same_v<CharT, char>)
return copy(txt.ascii(), out).out;
else
throw std::invalid_argument("ASCII text can't be copied to CharT output");
}
}

template<typename CharT, std::size_t N, std::size_t M, std::output_iterator<CharT> Out>
constexpr Out copy_symbol(const basic_symbol_text<N, M>& txt, text_encoding encoding, bool negative_power, Out out)
{
out = copy<CharT>(txt, encoding, out);
if (negative_power) {
constexpr auto exp = superscript<-1>();
out = copy<CharT>(exp, encoding, out);
}
return out;
}

template<typename CharT, int Num, int... Den, std::output_iterator<CharT> Out>
constexpr Out copy_symbol_exponent(text_encoding encoding, bool negative_power, Out out)
{
constexpr ratio r{Num, Den...};
if constexpr (r.den != 1) {
// add root part
if (negative_power) {
constexpr auto txt = basic_symbol_text("^-(") + regular<r.num>() + basic_symbol_text("/") + regular<r.den>() +
basic_symbol_text(")");
return copy<CharT>(txt, encoding, out);
} else {
constexpr auto txt =
basic_symbol_text("^(") + regular<r.num>() + basic_symbol_text("/") + regular<r.den>() + basic_symbol_text(")");
return copy<CharT>(txt, encoding, out);
}
} else if constexpr (r.num != 1) {
// add exponent part
if (negative_power) {
constexpr auto txt = superscript<-r.num>();
return copy<CharT>(txt, encoding, out);
} else {
constexpr auto txt = superscript<r.num>();
return copy<CharT>(txt, encoding, out);
}
}
}

} // namespace detail

} // namespace mp_units
113 changes: 107 additions & 6 deletions src/core/include/mp-units/dimension.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <mp-units/bits/external/type_traits.h>
#include <mp-units/bits/module_macros.h>
#include <mp-units/bits/symbol_text.h>
#include <mp-units/bits/text_tools.h>

namespace mp_units {

Expand Down Expand Up @@ -164,13 +165,15 @@ template<auto Symbol>

} // namespace detail

MP_UNITS_EXPORT template<Dimension Lhs, Dimension Rhs>
MP_UNITS_EXPORT_BEGIN

template<Dimension Lhs, Dimension Rhs>
[[nodiscard]] consteval bool operator==(Lhs lhs, Rhs rhs)
{
return is_same_v<Lhs, Rhs> || detail::derived_from_the_same_base_dimension(lhs, rhs);
}

MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto inverse(Dimension auto d) { return dimension_one / d; }
[[nodiscard]] consteval Dimension auto inverse(Dimension auto d) { return dimension_one / d; }

/**
* @brief Computes the value of a dimension raised to the `Num/Den` power
Expand All @@ -181,7 +184,7 @@ MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto inverse(Dimension auto d)
*
* @return Dimension The result of computation
*/
MP_UNITS_EXPORT template<std::intmax_t Num, std::intmax_t Den = 1, Dimension D>
template<std::intmax_t Num, std::intmax_t Den = 1, Dimension D>
requires detail::non_zero<Den>
[[nodiscard]] consteval Dimension auto pow(D d)
{
Expand All @@ -202,7 +205,7 @@ MP_UNITS_EXPORT template<std::intmax_t Num, std::intmax_t Den = 1, Dimension D>
*
* @return Dimension The result of computation
*/
MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto sqrt(Dimension auto d) { return pow<1, 2>(d); }
[[nodiscard]] consteval Dimension auto sqrt(Dimension auto d) { return pow<1, 2>(d); }

/**
* @brief Computes the cubic root of a dimension
Expand All @@ -211,9 +214,107 @@ MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto sqrt(Dimension auto d) {
*
* @return Dimension The result of computation
*/
MP_UNITS_EXPORT [[nodiscard]] consteval Dimension auto cbrt(Dimension auto d) { return pow<1, 3>(d); }
[[nodiscard]] consteval Dimension auto cbrt(Dimension auto d) { return pow<1, 3>(d); }


struct dimension_symbol_formatting {
text_encoding encoding = text_encoding::default_encoding;
};

MP_UNITS_EXPORT_END

namespace detail {

template<typename CharT, std::output_iterator<CharT> Out, Dimension D>
requires requires { D::symbol; }
constexpr Out dimension_symbol_impl(Out out, D, dimension_symbol_formatting fmt, bool negative_power)
{
return copy_symbol<CharT>(D::symbol, fmt.encoding, negative_power, out);
}

template<typename CharT, std::output_iterator<CharT> Out, typename F, int Num, int... Den>
constexpr auto dimension_symbol_impl(Out out, const power<F, Num, Den...>&, dimension_symbol_formatting fmt,
bool negative_power)
{
out = dimension_symbol_impl<CharT>(out, F{}, fmt, false); // negative power component will be added below if needed
return copy_symbol_exponent<CharT, Num, Den...>(fmt.encoding, negative_power, out);
}

template<typename CharT, std::output_iterator<CharT> Out, DerivedDimensionExpr... Ms>
constexpr Out dimension_symbol_impl(Out out, const type_list<Ms...>&, dimension_symbol_formatting fmt,
bool negative_power)
{
return (..., (out = dimension_symbol_impl<CharT>(out, Ms{}, fmt, negative_power)));
}

template<typename CharT, std::output_iterator<CharT> Out, DerivedDimensionExpr... Nums, DerivedDimensionExpr... Dens>
constexpr Out dimension_symbol_impl(Out out, const type_list<Nums...>& nums, const type_list<Dens...>& dens,
dimension_symbol_formatting fmt)
{
if constexpr (sizeof...(Nums) == 0 && sizeof...(Dens) == 0) {
// dimensionless quantity
*out++ = '1';
return out;
} else if constexpr (sizeof...(Dens) == 0) {
// no denominator
return dimension_symbol_impl<CharT>(out, nums, fmt, false);
} else {
if constexpr (sizeof...(Nums) > 0) out = dimension_symbol_impl<CharT>(out, nums, fmt, false);
return dimension_symbol_impl<CharT>(out, dens, fmt, true);
}
}

// TODO consider adding the support for text output of the dimensional equation
template<typename CharT, std::output_iterator<CharT> Out, typename... Expr>
constexpr Out dimension_symbol_impl(Out out, const derived_dimension<Expr...>&, dimension_symbol_formatting fmt,
bool negative_power)
{
gsl_Expects(negative_power == false);
return dimension_symbol_impl<CharT>(out, typename derived_dimension<Expr...>::_num_{},
typename derived_dimension<Expr...>::_den_{}, fmt);
}


} // namespace detail

MP_UNITS_EXPORT template<typename CharT = char, std::output_iterator<CharT> Out, Dimension D>
constexpr Out dimension_symbol_to(Out out, D d, dimension_symbol_formatting fmt = dimension_symbol_formatting{})
{
return detail::dimension_symbol_impl<CharT>(out, d, fmt, false);
}

namespace detail {

template<typename CharT, std::size_t N, dimension_symbol_formatting fmt, Dimension D>
[[nodiscard]] consteval std::array<CharT, N + 1> get_symbol_buffer(D)
{
std::array<CharT, N + 1> buffer{};
dimension_symbol_to<CharT>(buffer.begin(), D{}, fmt);
return buffer;
}

} // namespace detail


// TODO Refactor to `dimension_symbol(D, fmt)` when P1045: constexpr Function Parameters is available
MP_UNITS_EXPORT template<dimension_symbol_formatting fmt = dimension_symbol_formatting{}, typename CharT = char,
Dimension D>
[[nodiscard]] consteval auto dimension_symbol(D)
{
auto get_size = []() consteval {
std::basic_string<CharT> buffer;
dimension_symbol_to<CharT>(std::back_inserter(buffer), D{}, fmt);
return buffer.size();
};

#if __cpp_constexpr >= 202211L // Permitting static constexpr variables in constexpr functions
static constexpr std::size_t size = get_size();
static constexpr auto buffer = detail::get_symbol_buffer<CharT, size, fmt>(D{});
return std::string_view(buffer.data(), size);
#else
constexpr std::size_t size = get_size();
constexpr auto buffer = detail::get_symbol_buffer<CharT, size, fmt>(D{});
return basic_fixed_string(buffer.data(), std::integral_constant<std::size_t, size>{});
#endif
}

} // namespace mp_units
87 changes: 80 additions & 7 deletions src/core/include/mp-units/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,80 @@ OutputIt format_global_buffer(OutputIt out, const fill_align_width_format_specs<
// dimension-spec ::= [text-encoding]
// text-encoding ::= 'U' | 'A'
//
template<mp_units::Dimension D, typename Char>
class MP_UNITS_STD_FMT::formatter<D, Char> {
struct format_specs : mp_units::detail::fill_align_width_format_specs<Char>, mp_units::dimension_symbol_formatting {};

// template<typename Char>
// struct dimension_format_specs : fill_align_width_format_specs<Char>, dimension_symbol_formatting {};
std::basic_string_view<Char> fill_align_width_format_str_;
std::basic_string_view<Char> modifiers_format_str_;
format_specs specs_{};

struct format_checker {
using enum mp_units::text_encoding;
mp_units::text_encoding encoding = unicode;
constexpr void on_text_encoding(Char val) { encoding = (val == 'U') ? unicode : ascii; }
};

struct unit_formatter {
format_specs specs;
using enum mp_units::text_encoding;
constexpr void on_text_encoding(Char val) { specs.encoding = (val == 'U') ? unicode : ascii; }
};

template<typename Handler>
constexpr const Char* parse_dimension_specs(const Char* begin, const Char* end, Handler&& handler) const
{
auto it = begin;
if (it == end || *it == '}') return begin;

constexpr auto valid_modifiers = std::string_view{"UA"};
for (; it != end && *it != '}'; ++it) {
if (valid_modifiers.find(*it) == std::string_view::npos)
throw MP_UNITS_STD_FMT::format_error("invalid dimension modifier specified");
}
end = it;

if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) handler.on_text_encoding(*it);
return end;
}

public:
constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin())
{
const auto begin = ctx.begin();
auto end = ctx.end();

auto it = parse_fill_align_width(ctx, begin, end, specs_);
fill_align_width_format_str_ = {begin, it};
if (it == end) return it;

format_checker checker;
end = parse_dimension_specs(it, end, checker);
modifiers_format_str_ = {it, end};
return end;
}

template<typename FormatContext>
auto format(const D& d, FormatContext& ctx) const -> decltype(ctx.out())
{
unit_formatter f{specs_};
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(f.specs.width, f.specs.width_ref, ctx);

parse_dimension_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f);

if (f.specs.width == 0) {
// Avoid extra copying if width is not specified
return mp_units::dimension_symbol_to<Char>(ctx.out(), d, f.specs);
} else {
std::basic_string<Char> unit_buffer;
mp_units::dimension_symbol_to<Char>(std::back_inserter(unit_buffer), d, f.specs);

std::basic_string<Char> global_format_buffer = "{:" + std::basic_string<Char>{fill_align_width_format_str_} + "}";
return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer,
MP_UNITS_STD_FMT::make_format_args(unit_buffer));
}
}
};


//
Expand Down Expand Up @@ -297,10 +368,9 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
return on_replacement_field<Rep>(begin);
else if (id == "U")
return on_replacement_field<unit_t>(begin);
else if (id == "D") {
return begin;
// on_replacement_field<dimension_t>(begin);
} else
else if (id == "D")
return on_replacement_field<dimension_t>(begin);
else
throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'");
}

Expand Down Expand Up @@ -337,7 +407,10 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
{
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.unit));
}
void on_dimension(std::basic_string_view<Char>) {}
void on_dimension(std::basic_string_view<Char> format_str)
{
out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.dimension));
}
void on_text(const Char* begin, const Char* end) const { std::copy(begin, end, out); }

constexpr const Char* on_replacement_field(std::basic_string_view<Char> id, const Char* begin)
Expand Down
Loading

0 comments on commit e50d75a

Please sign in to comment.