Files
mp-units/src/include/units/format.h
T

439 lines
14 KiB
C++
Raw Normal View History

2019-10-16 17:04:41 +02:00
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <units/customization_points.h>
2019-10-18 23:50:43 +02:00
#include <units/quantity.h>
#include <fmt/format.h>
2019-11-12 19:46:15 +01:00
#include <string_view>
2019-10-16 17:04:41 +02:00
2020-03-19 21:39:12 +01:00
// Grammar
//
// units-format-spec ::= [fill-and-align] [width] [units-specs]
// units-specs ::= conversion-spec
// units-specs conversion-spec
// units-specs literal-char
// literal-char ::= any character other than '{' or '}'
// conversion-spec ::= '%' units-type
// units-type ::= [units-rep-modifier] 'Q'
// [units-unit-modifier] 'q'
// one of "nt%"
// units-rep-modifier ::= [sign] [#] [precision] [units-rep-type]
// units-rep-type ::= one of "aAbBdeEfFgGoxX"
// units-unit-modifier ::= 'A'
2019-11-06 16:50:34 +00:00
namespace units {
2019-11-05 20:05:11 +00:00
2019-11-06 16:50:34 +00:00
namespace detail {
2019-11-04 06:37:50 +00:00
2020-03-19 21:39:12 +01:00
// Holds specs about the whole object
template <typename CharT>
struct global_format_specs
{
CharT fill = '\0';
fmt::align_t align = fmt::align_t::right; // quantity values should behave like numbers (by default aligned to right)
int width = -1;
};
2019-11-06 16:50:34 +00:00
2020-03-19 21:39:12 +01:00
// Holds specs about the representation (%[specs]Q)
struct rep_format_specs
{
fmt::sign_t sign = fmt::sign_t::none;
bool alt = false;
int precision = -1;
char type = '\0';
};
// Holds specs about the unit (%[specs]q)
struct unit_format_specs
{
char modifier = '\0';
};
// Parse a `units-rep-modifier`
template <typename CharT, typename Handler>
constexpr const CharT* parse_units_rep(const CharT* begin, const CharT* end, Handler&& handler, bool treat_as_floating_point)
{
// parse sign
switch(static_cast<char>(*begin)) {
case '+':
handler.on_plus();
++begin;
break;
case '-':
handler.on_minus();
++begin;
break;
case ' ':
handler.on_space();
++begin;
break;
}
if(begin == end)
return begin;
// parse #
if (*begin == '#') {
handler.on_alt();
if (++begin == end) return begin;
}
// parse precision if a floating point
if(*begin == '.') {
if (treat_as_floating_point)
begin = fmt::internal::parse_precision(begin, end, handler);
else
handler.on_error("precision not allowed for integral quantity representation");
}
if(*begin != '}' && *begin != '%') {
handler.on_type(*begin++);
}
return begin;
}
// parse units-specs
2019-11-06 16:50:34 +00:00
template<typename CharT, typename Handler>
constexpr const CharT* parse_units_format(const CharT* begin, const CharT* end, Handler&& handler)
{
auto ptr = begin;
while(ptr != end) {
auto c = *ptr;
if(c == '}')
break;
if(c != '%') {
++ptr;
continue;
}
if(begin != ptr)
2019-11-06 16:50:34 +00:00
handler.on_text(begin, ptr);
2020-03-19 21:39:12 +01:00
begin = ++ptr; // consume '%'
2019-11-06 16:50:34 +00:00
if(ptr == end)
throw fmt::format_error("invalid format");
c = *ptr++;
switch(c) {
2020-03-19 21:39:12 +01:00
// units-type
2019-11-06 16:50:34 +00:00
case '%':
handler.on_text(ptr - 1, ptr);
break;
case 'n': {
const char newline[] = "\n";
handler.on_text(newline, newline + 1);
break;
}
case 't': {
const char tab[] = "\t";
handler.on_text(tab, tab + 1);
break;
}
default:
2020-03-19 21:39:12 +01:00
constexpr auto Qq = std::string_view{"Qq"};
auto const new_end = std::find_first_of(begin, end, Qq.begin(), Qq.end());
if (new_end == end) {
throw fmt::format_error("invalid format");
}
if (*new_end == 'Q') {
handler.on_quantity_value(begin, new_end);
} else {
handler.on_quantity_unit(*begin);
}
ptr = new_end + 1;
2019-11-06 16:50:34 +00:00
}
begin = ptr;
}
if(begin != ptr)
handler.on_text(begin, ptr);
return ptr;
}
2020-03-19 21:39:12 +01:00
// build the 'representation' as requested in the format string, applying only units-rep-modifiers
template<typename Rep, typename OutputIt, typename CharT>
2020-03-19 21:39:12 +01:00
inline OutputIt format_units_quantity_value(OutputIt out, const Rep& val, const rep_format_specs& rep_specs)
2019-11-06 16:50:34 +00:00
{
2020-03-19 21:39:12 +01:00
fmt::basic_memory_buffer<CharT> buffer;
auto to_buffer = std::back_inserter(buffer);
fmt::format_to(to_buffer, "{{:");
switch(rep_specs.sign) {
2019-11-12 19:46:15 +01:00
case fmt::sign::none:
break;
case fmt::sign::plus:
2020-03-19 21:39:12 +01:00
format_to(to_buffer, "+");
2019-11-12 19:46:15 +01:00
break;
case fmt::sign::minus:
2020-03-19 21:39:12 +01:00
format_to(to_buffer, "-");
2019-11-12 19:46:15 +01:00
break;
case fmt::sign::space:
2020-03-19 21:39:12 +01:00
format_to(to_buffer, " ");
2019-11-12 19:46:15 +01:00
break;
}
2020-03-19 21:39:12 +01:00
if (rep_specs.alt) {
format_to(to_buffer, "#");
}
2020-03-19 21:39:12 +01:00
auto type = rep_specs.type;
if (auto precision = rep_specs.precision; precision >= 0) {
format_to(to_buffer, ".{}{}", precision, type == '\0' ? 'f' : type);
} else if constexpr (treat_as_floating_point<Rep>) {
format_to(to_buffer, "{}", type == '\0' ? 'g' : type);
} else {
if (type != '\0') {
format_to(to_buffer, "{}", type);
}
2020-03-17 13:04:09 +01:00
}
2020-03-19 21:39:12 +01:00
fmt::format_to(to_buffer, "}}");
return format_to(out, fmt::to_string(buffer), val);
2019-11-06 16:50:34 +00:00
}
template<typename OutputIt, typename Dimension, typename Unit, typename Rep, typename CharT>
2019-11-06 16:50:34 +00:00
struct units_formatter {
OutputIt out;
Rep val;
2020-03-19 21:39:12 +01:00
global_format_specs<CharT> const & global_specs;
rep_format_specs const & rep_specs;
unit_format_specs const & unit_specs;
2019-11-06 16:50:34 +00:00
2020-03-19 21:39:12 +01:00
explicit units_formatter(
OutputIt o, quantity<Dimension, Unit, Rep> q,
global_format_specs<CharT> const & gspecs,
rep_format_specs const & rspecs, unit_format_specs const & uspecs
):
out(o), val(q.count()), global_specs(gspecs), rep_specs(rspecs), unit_specs(uspecs)
2019-11-06 16:50:34 +00:00
{
}
template<typename CharT2>
void on_text(const CharT2* begin, const CharT2* end)
2019-11-06 16:50:34 +00:00
{
std::copy(begin, end, out);
}
2020-03-19 21:39:12 +01:00
void on_quantity_value([[maybe_unused]] const CharT*, [[maybe_unused]] const CharT*)
2019-11-06 16:50:34 +00:00
{
2020-03-19 21:39:12 +01:00
out = format_units_quantity_value(out, val, global_specs, rep_specs);
2019-11-06 16:50:34 +00:00
}
2020-03-19 21:39:12 +01:00
void on_quantity_unit([[maybe_unused]] const CharT)
2019-11-06 16:50:34 +00:00
{
2020-03-19 21:39:12 +01:00
if (unit_specs.modifier != '\0') {
throw fmt::format_error(
fmt::format("Unit modifier '{}' is not implemented", unit_specs.modifier)
); // TODO
}
format_to(out, "{}", unit_text<Dimension, Unit>().c_str());
2019-11-06 16:50:34 +00:00
}
};
} // namespace detail
} // namespace units
template<typename Dimension, typename Unit, typename Rep, typename CharT>
struct fmt::formatter<units::quantity<Dimension, Unit, Rep>, CharT> {
2019-11-05 20:05:11 +00:00
private:
using quantity = units::quantity<Dimension, Unit, Rep>;
using iterator = fmt::basic_format_parse_context<CharT>::iterator;
2019-11-06 16:50:34 +00:00
using arg_ref_type = fmt::internal::arg_ref<CharT>;
2020-03-19 21:39:12 +01:00
units::detail::global_format_specs<CharT> global_specs;
units::detail::rep_format_specs rep_specs;
units::detail::unit_format_specs unit_specs;
2019-11-12 16:55:11 +01:00
bool quantity_value = false;
bool quantity_unit = false;
2019-11-05 20:05:11 +00:00
arg_ref_type width_ref;
arg_ref_type precision_ref;
2019-11-06 16:50:34 +00:00
fmt::basic_string_view<CharT> format_str;
2019-11-05 20:05:11 +00:00
2019-11-06 16:50:34 +00:00
struct spec_handler {
formatter& f;
fmt::basic_format_parse_context<CharT>& context;
2019-11-06 16:50:34 +00:00
fmt::basic_string_view<CharT> format_str;
template<typename Id>
constexpr arg_ref_type make_arg_ref(Id arg_id)
{
context.check_arg_id(arg_id);
return arg_ref_type(arg_id);
}
constexpr arg_ref_type make_arg_ref(fmt::basic_string_view<CharT> arg_id)
{
context.check_arg_id(arg_id);
return arg_ref_type(arg_id);
2019-11-06 16:50:34 +00:00
}
constexpr arg_ref_type make_arg_ref(fmt::internal::auto_id)
2019-11-06 16:50:34 +00:00
{
return arg_ref_type(context.next_arg_id());
}
void on_error(const char* msg) { throw fmt::format_error(msg); }
2020-03-19 21:39:12 +01:00
constexpr void on_fill(CharT fill) { f.global_specs.fill = fill; } // global
constexpr void on_align(align_t align) { f.global_specs.align = align; } // global
constexpr void on_width(int width) { f.global_specs.width = width; } // global
constexpr void on_plus() { f.rep_specs.sign = fmt::sign::plus; } // rep
constexpr void on_minus() { f.rep_specs.sign = fmt::sign::minus; } // rep
constexpr void on_space() { f.rep_specs.sign = fmt::sign::space; } // rep
constexpr void on_alt() { f.rep_specs.alt = true; } // rep
constexpr void on_precision(int precision) { f.rep_specs.precision = precision; } // rep
constexpr void on_type(char type) // rep
2020-03-17 13:04:09 +01:00
{
constexpr auto good_types = std::string_view{"aAbBdeEfFgGoxX"};
2020-03-17 13:04:09 +01:00
if (good_types.find(type) != std::string_view::npos) {
2020-03-19 21:39:12 +01:00
f.rep_specs.type = type;
} else {
on_error("invalid type specifier");
2020-03-17 13:04:09 +01:00
}
}
2020-03-19 21:39:12 +01:00
constexpr void on_modifier(char mod) { f.unit_specs.modifier = mod; } // unit
2019-11-12 19:46:15 +01:00
constexpr void end_precision() {}
2019-11-06 16:50:34 +00:00
template<typename Id>
2019-11-12 19:46:15 +01:00
constexpr void on_dynamic_width(Id arg_id)
2019-11-06 16:50:34 +00:00
{
f.width_ref = make_arg_ref(arg_id);
}
template<typename Id>
2019-11-12 19:46:15 +01:00
constexpr void on_dynamic_precision(Id arg_id)
2019-11-06 16:50:34 +00:00
{
f.precision_ref = make_arg_ref(arg_id);
}
2019-11-12 16:55:11 +01:00
2019-11-12 19:46:15 +01:00
constexpr void on_text(const CharT*, const CharT*) {}
2020-03-19 21:39:12 +01:00
constexpr void on_quantity_value(const CharT* begin, const CharT* end)
{
if (begin != end) {
units::detail::parse_units_rep(begin, end, *this, units::treat_as_floating_point<Rep>);
}
f.quantity_value = true;
}
constexpr void on_quantity_unit(const CharT mod)
{
if (mod != 'q') {
f.unit_specs.modifier = mod;
}
f.quantity_unit = true;
}
2019-11-06 16:50:34 +00:00
};
struct parse_range {
iterator begin;
iterator end;
};
constexpr parse_range do_parse(fmt::basic_format_parse_context<CharT>& ctx)
2019-11-06 16:50:34 +00:00
{
auto begin = ctx.begin(), end = ctx.end();
if(begin == end || *begin == '}')
return {begin, begin};
// handler to assign parsed data to formatter data members
spec_handler handler{*this, ctx, format_str};
// parse alignment
begin = fmt::internal::parse_align(begin, end, handler);
if(begin == end)
return {begin, begin};
// parse width
begin = fmt::internal::parse_width(begin, end, handler);
if(begin == end)
return {begin, begin};
// parse units-specific specification
2019-11-12 16:55:11 +01:00
end = units::detail::parse_units_format(begin, end, handler);
2020-03-19 21:39:12 +01:00
if((quantity_unit && !quantity_value) && (rep_specs.sign == fmt::sign::plus || rep_specs.sign == fmt::sign::minus))
2019-11-12 19:46:15 +01:00
handler.on_error("sign not allowed for a quantity unit");
2019-11-06 16:50:34 +00:00
return {begin, end};
}
2019-11-05 20:05:11 +00:00
public:
constexpr auto parse(fmt::basic_format_parse_context<CharT>& ctx)
2019-11-05 20:05:11 +00:00
{
2019-11-06 16:50:34 +00:00
auto range = do_parse(ctx);
format_str = fmt::basic_string_view<CharT>(&*range.begin, fmt::internal::to_unsigned(range.end - range.begin));
return range.end;
2019-11-05 20:05:11 +00:00
}
2019-10-16 17:04:41 +02:00
2019-10-18 23:50:43 +02:00
template<typename FormatContext>
auto format(const units::quantity<Dimension, Unit, Rep>& q, FormatContext& ctx)
2019-10-18 23:50:43 +02:00
{
2019-11-06 16:50:34 +00:00
auto begin = format_str.begin(), end = format_str.end();
// TODO Avoid extra copying if width is not specified
fmt::basic_memory_buffer<CharT> buf;
auto out = std::back_inserter(buf);
// process dynamic width and precision
2020-03-19 21:39:12 +01:00
fmt::internal::handle_dynamic_spec<fmt::internal::width_checker>(global_specs.width, width_ref, ctx);
fmt::internal::handle_dynamic_spec<fmt::internal::precision_checker>(rep_specs.precision, precision_ref, ctx);
2019-11-06 16:50:34 +00:00
// deal with quantity content
if(begin == end || *begin == '}') {
// default format should print value followed by the unit separated with 1 space
2020-03-19 21:39:12 +01:00
out = units::detail::format_units_quantity_value(out, q.count(), global_specs, rep_specs);
constexpr auto symbol = units::detail::unit_text<Dimension, Unit>();
if(symbol.size()) {
*out++ = CharT(' ');
format_to(out, "{}", symbol.c_str());
}
2020-03-19 21:39:12 +01:00
return format_to(ctx.out(), fmt::to_string(buf));
2019-11-06 16:50:34 +00:00
}
else {
// user provided format
2020-03-19 21:39:12 +01:00
units::detail::units_formatter f(out, q, global_specs, rep_specs, unit_specs);
2019-11-06 16:50:34 +00:00
parse_units_format(begin, end, f);
2020-03-19 21:39:12 +01:00
fmt::basic_memory_buffer<CharT> outer;
auto to_outer = std::back_inserter(outer);
format_to(to_outer, "{{:");
if (auto fill = global_specs.fill; fill != '\0') {
format_to(to_outer, "{}", fill);
}
if (auto align = global_specs.align; align != fmt::align_t::none) {
switch (align) {
case fmt::align_t::left:
format_to(to_outer, "<");
break;
case fmt::align_t::right:
format_to(to_outer, ">");
break;
case fmt::align_t::center:
format_to(to_outer, "^");
break;
default:
break;
}
}
if (auto width = global_specs.width; width >= 0) {
format_to(to_outer, "{}", width);
}
format_to(to_outer, "}}");
return format_to(ctx.out(), fmt::to_string(outer), fmt::to_string(buf));
2019-11-06 16:50:34 +00:00
}
2019-10-16 17:04:41 +02:00
}
2019-10-18 23:50:43 +02:00
};