diff options
| author | Markus Mittendrein <maxmitti@maxmitti.tk> | 2022-01-07 18:40:42 +0100 |
|---|---|---|
| committer | Markus Mittendrein <maxmitti@maxmitti.tk> | 2022-01-07 22:50:08 +0100 |
| commit | cb71e001e5088e9006c00634ce833f5659af855c (patch) | |
| tree | 1bb004bdbbae91442437ca0e93104f17494cc2af | |
| parent | 4f2eb7c1dfe467aa2d7a6652a111edc74aa7b0a1 (diff) | |
| download | cxxformat-cb71e001e5088e9006c00634ce833f5659af855c.tar.gz cxxformat-cb71e001e5088e9006c00634ce833f5659af855c.zip | |
Add runtime implementation and refactor format_spec checks in formatters
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | include/cxxformat/core.hpp | 27 | ||||
| -rw-r--r-- | include/cxxformat/cxxformat | 1 | ||||
| -rw-r--r-- | include/cxxformat/formatters.hpp | 247 | ||||
| -rw-r--r-- | include/cxxformat/runtime.hpp | 219 | ||||
| -rw-r--r-- | main.cpp | 13 |
6 files changed, 396 insertions, 112 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e0e9cb..46720d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(cxxformat INTERFACE include/cxxformat/formatters.hpp include/cxxformat/helpers.hpp include/cxxformat/ostream.hpp + include/cxxformat/runtime.hpp include/cxxformat/string.hpp ) target_include_directories(cxxformat INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/include/cxxformat/core.hpp b/include/cxxformat/core.hpp index 8669f1c..993060d 100644 --- a/include/cxxformat/core.hpp +++ b/include/cxxformat/core.hpp @@ -1,5 +1,6 @@ #pragma once +#include <algorithm> #include <optional> #include <stdexcept> #include <string_view> @@ -155,17 +156,17 @@ namespace format { template<typename T, bool fallback = false> struct formatter; - template<typename T> - concept has_formatter = requires(T t, format_specifier spec, const NullOutput& out, optional_int<std::size_t> z) { - { formatter<T>::format(out, t, spec, z, z) } -> std::same_as<void>; - { formatter<T>::template format<format_specifier{}>(out, t, z, z) } -> std::same_as<void>; + template<typename Formatter, typename Value> + concept is_formatter = requires(Value t, format_specifier spec, const NullOutput& out, optional_int<std::size_t> z) { + { Formatter::format(out, t, spec, z, z) } -> std::same_as<void>; + { Formatter::conversionSupported(spec) } -> std::same_as<void>; }; template<typename T> - concept has_fallback_formatter = requires(T t, format_specifier spec, const NullOutput& out, optional_int<std::size_t> z) { - { formatter<T, true>::format(out, t, spec, z, z) } -> std::same_as<void>; - { formatter<T, true>::template format<format_specifier{}>(out, t, z, z) } -> std::same_as<void>; - }; + concept has_formatter = is_formatter<formatter<T>, T>; + + template<typename T> + concept has_fallback_formatter = is_formatter<formatter<T, true>, T>; template<typename T> concept has_some_formatter = has_formatter<T> || has_fallback_formatter<T>; @@ -464,8 +465,16 @@ namespace format { static_assert(sizeof(Arg) <= sizeof(std::size_t), "Precision argument type must be at most as big as std::size_t"); precision = nth_argument<*spec.precision>(std::forward<Args>(args)...); } + auto&& arg = nth_argument<spec.argIndex>(std::forward<Args>(args)...); - return formatter_with_fallback<std::decay_t<decltype(arg)>>::template format<spec>(out, arg, minWidth, precision); + using formatter = formatter_with_fallback<std::decay_t<decltype(arg)>>; + + []() consteval { + // throws on error + formatter::conversionSupported(spec); + }(); + + return formatter::format(out, arg, spec, minWidth, precision); }; (([&]{ diff --git a/include/cxxformat/cxxformat b/include/cxxformat/cxxformat index 6da253a..72bef6f 100644 --- a/include/cxxformat/cxxformat +++ b/include/cxxformat/cxxformat @@ -4,4 +4,5 @@ #include <cxxformat/formatters.hpp> #include <cxxformat/file_ptr.hpp> #include <cxxformat/ostream.hpp> +#include <cxxformat/runtime.hpp> #include <cxxformat/string.hpp> diff --git a/include/cxxformat/formatters.hpp b/include/cxxformat/formatters.hpp index a713018..ea0c08b 100644 --- a/include/cxxformat/formatters.hpp +++ b/include/cxxformat/formatters.hpp @@ -9,10 +9,18 @@ #include <charconv> #include <concepts> #include <limits> +#include <memory> #include <sstream> +#include <system_error> namespace format { namespace { + template<str what> + constexpr auto operator ""_contains() noexcept + { + return [](char c) constexpr noexcept { return std::string_view{what}.find(c) != std::string_view::npos; }; + } + constexpr void formatPadded(const format_output auto& out, std::string_view subject, char padding, bool leftJustified, optional_int<std::size_t> minWidth) { if(subject.length() < minWidth.value_or(0) && !leftJustified) @@ -27,6 +35,51 @@ namespace format { out(padding, *minWidth - subject.length()); } } + + constexpr void simpleConversionSpecifierCheck(std::string_view supported, char c, std::string_view typeDesc) + { + using namespace std::string_literals; + if (supported.find(c) == std::string_view::npos) + { + throw std::invalid_argument{"Unsupported conversion specifier ‘"s + c + "’ for "s + std::string{typeDesc} + "; Supported are "s + std::string{supported}}; + } + } + + constexpr void noAlternative(bool alternative, std::string_view typeDesc) + { + using namespace std::string_literals; + if (alternative) + { + throw std::invalid_argument{"‘#’ (alternative) flag is not supported for "s + std::string{typeDesc}}; + } + } + + constexpr void noSignFlags(char addSign, std::string_view typeDesc) + { + using namespace std::string_literals; + if (addSign != '\0') + { + throw std::invalid_argument{"‘+’ and ‘ ’ (sign related) flags are not supported for %s"s + std::string{typeDesc}}; + } + } + + constexpr void noZeroPadding(char padding, std::string_view typeDesc) + { + using namespace std::string_literals; + if (padding == '0') + { + throw std::invalid_argument{"Zero padding (‘0’ flag) is not supported for %s"s + std::string{typeDesc}}; + } + } + + constexpr void noPrecision(optional_int<std::size_t> precision, std::string_view typeDesc) + { + using namespace std::string_literals; + if (precision) + { + throw std::invalid_argument{"Specifying a precision is not supported for "s + std::string{typeDesc}}; + } + } } using invalid_argument = std::invalid_argument; @@ -35,54 +88,25 @@ namespace format { struct formatter<T> { static constexpr void format(const format_output auto& out, T t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) { - int base = 10; - switch(spec.conversion) + const auto conv = spec.conversion; + if (conv == 'c') { - case 'v': - break; - case 'c': - { - // TODO: see static_assert version - char converted = static_cast<char>(static_cast<unsigned char>(t)); - formatPadded(out, {&converted, 1}, ' ', spec.leftJustified, minWidth); - return; - } - case 'i': case 'd': - if constexpr (std::unsigned_integral<T>) - { - throw invalid_argument{"%i and %d are not supported for unsigned integrals"}; - } - break; - case 'o': - base = 8; - if constexpr (std::signed_integral<T>) - { - throw invalid_argument{"%o is not supported for signed integrals"}; - } - break; - case 'x': case 'X': - base = 16; - if constexpr (std::signed_integral<T>) - { - throw invalid_argument{"%x and %X are not supported for signed integrals"}; - } - break; - case 'u': - if constexpr (std::signed_integral<T>) - { - throw invalid_argument{"%u is not supported for signed integrals"}; - } - break; - default: - throw invalid_argument{std::string{"The given integral type does not support conversion specifier ‘"} + spec.conversion + std::string{"’"}}; + char converted = static_cast<char>(static_cast<unsigned char>(t)); + formatPadded(out, {&converted, 1}, ' ', spec.leftJustified, minWidth); + return; } + const int base = std::unsigned_integral<T> ? (conv == 'o' ? 8 : "xX"_contains(conv) ? 16 : 10) : 10; + std::string_view converted; std::array<char, (std::numeric_limits<T>::digits + 2) / 3> result; if (precision != 0 || t != 0) { const auto [end, ec] = std::to_chars(result.data(), result.data() + result.size(), t, base); - assert(ec == std::errc{}); + if (ec != std::errc{}) + { + throw std::runtime_error{"Unexpected error from to_chars: " + std::make_error_condition(ec).message()}; + } converted = {result.data(), end}; } const bool hasMinusSign = !std::unsigned_integral<T> && converted.starts_with('-'); @@ -175,77 +199,96 @@ namespace format { return; } - template<format_specifier spec> - static constexpr void format(const format_output auto& out, T t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void conversionSupported(format_specifier spec) { - constexpr auto conv = spec.conversion; - if constexpr (conv == 'c') + const auto conv = spec.conversion; + + // TODO: what is the right thing to do for %c? + // static_assert(std::same_as<T, int>, "%c only accepts int as argument"); + + if constexpr (std::signed_integral<T>) { - // TODO: what is the right thing to do here? - // static_assert(std::same_as<T, int>, "%c only accepts int as argument"); + simpleConversionSpecifierCheck("cidv", conv, "signed integral type"); } - else if constexpr (conv == 'i' || conv == 'd') + else { - static_assert(std::signed_integral<T>, "%i and %d only accept signed integral types"); + static_assert(std::unsigned_integral<T>, "Integral type is neither signed nor unsigned?"); + simpleConversionSpecifierCheck("coxXuv", conv, "unsigned integral type"); } - else if constexpr (conv == 'o' || conv == 'x' || conv == 'X' || conv == 'u') + + if (spec.alternative && !"oxX"_contains(conv)) { - static_assert(std::unsigned_integral<T>, "%o, %x, %X and %u only accept unsigned integral types"); + throw std::invalid_argument{"‘#’ (alternative) flag is only allowed for %x, %X and %o"}; } - else if constexpr (conv != 'v') + + if (conv == 'c') { - static_assert(sizeof(T) == 0, "Unsupported conversion for integral type specified"); + noPrecision(spec.precision, "integral types with %c"); } - static_assert(spec.addSign == '\0' || conv == 'i' || conv == 'd', "‘+’ and ‘ ’ (sign related) flags are only allowed for %i and %d"); - static_assert(!spec.alternative || conv == 'o' || conv == 'x' || conv == 'X', "‘#’ (alternative) flag is only allowed for %x, %X and %o"); - - return format(out, t, spec, minWidth, precision); + if (spec.addSign != '\0' && !(std::signed_integral<T> && "idv"_contains(conv))) + { + throw std::invalid_argument{"‘+’ and ‘ ’ (sign related) flags are only allowed for signed integral types and conversions %i, %d and %v"}; + } } }; // NOTE: Difference to printf: uses std::to_chars’ minimal width for exact representation when no precision is specified instead of default precision of 6 template<std::floating_point T> struct formatter<T> { - static constexpr void format(const format_output auto& out, T t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static void format(const format_output auto& out, T t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) { std::chars_format format; - bool uppercase = false; + const auto uppercase = "AEFG"_contains(spec.conversion); switch(spec.conversion) { case 'A': - uppercase = true; - [[fallthrough]]; case 'a': format = std::chars_format::hex; break; case 'E': - uppercase = true; - [[fallthrough]]; case 'e': format = std::chars_format::scientific; break; case 'F': - uppercase = true; - [[fallthrough]]; case 'f': format = std::chars_format::fixed; break; case 'G': - uppercase = true; - [[fallthrough]]; case 'g': case 'v': format = std::chars_format::general; break; default: - throw invalid_argument{std::string{"The given floating point type does not support conversion specifier ‘"} + spec.conversion + std::string{"’"}}; + assert(!"Unsupported conversion; should have been caught by formatter<T>::conversionSupported"); } + char* data, *end; + std::errc ec; + const auto call_to_chars = [format, precision, t, &data, &end, &ec](char* data_, std::size_t size) + { + data = data_; + const auto [end_, ec_] = precision ? std::to_chars(data, data + size, t, format, *precision) : std::to_chars(data, data + size, t, format); + end = end_; + ec = ec_; + }; + std::array<char, (std::numeric_limits<T>::digits10 * 2 + 6)> result; - const auto [end, ec] = precision ? std::to_chars(result.data(), result.data() + result.size(), t, format ,*precision) : std::to_chars(result.data(), result.data() + result.size(), t, format); - assert(ec == std::errc{}); - std::string_view converted{result.data(), end}; + std::unique_ptr<char[]> largeResult; + call_to_chars(result.data(), result.size()); + if (ec == std::errc::value_too_large && precision) + { + const auto size = result.size() + *precision; + largeResult = std::make_unique_for_overwrite<char[]>(size); + call_to_chars(largeResult.get(), size); + } + + if (ec != std::errc{}) + { + throw std::runtime_error{"Unexpected error from to_chars: " + std::make_error_condition(ec).message()}; + } + + std::string_view converted{data, end}; const bool hasMinusSign = converted.starts_with('-'); if (hasMinusSign) @@ -337,11 +380,9 @@ namespace format { return; } - template<format_specifier spec> - static constexpr void format(const format_output auto& out, T t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void conversionSupported(format_specifier spec) { - static_assert(std::string_view{"fFeEaAgGv"}.find(spec.conversion) != std::string_view::npos, "Unsupported conversion specified for floating point type"); - return format(out, t, spec, minWidth, precision); + simpleConversionSpecifierCheck("fFeEaAgGv", spec.conversion, "floating point type"); } }; @@ -365,11 +406,11 @@ namespace format { formatPadded(out, val, ' ', spec.leftJustified, minWidth); } - template<format_specifier spec> - static constexpr void format(const format_output auto& out, const S& s, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void conversionSupported(format_specifier spec) { - static_assert(spec.conversion == 's' || spec.conversion == 'v', "The given stringy type only supports the ‘s’ and ‘v’ conversions"); - return format(out, s, spec, minWidth, precision); + simpleConversionSpecifierCheck("sv", spec.conversion, "stringy type"); + noAlternative(spec.alternative, "stringy types"); + noSignFlags(spec.addSign, "stringy types"); } }; @@ -378,7 +419,10 @@ namespace format { template<pointer T> struct formatter<T> { - static constexpr void format(const format_output auto& out, const std::remove_pointer_t<T>* t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr inline format_specifier delegateSpec{.conversion = 'x', .alternative = true}; + using delegated_formatter = formatter<std::uintptr_t>; + + static constexpr void format(const format_output auto& out, const std::remove_pointer_t<T>* t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t>) { if (t == nullptr) { @@ -386,31 +430,30 @@ namespace format { } else { - constexpr format_specifier spec{.conversion = 'x', .alternative = true}; - formatter<std::uintptr_t>::template format<spec>(out, std::bit_cast<std::uintptr_t>(t), minWidth, std::nullopt); + delegated_formatter::format(out, std::bit_cast<std::uintptr_t>(t), delegateSpec, minWidth, std::nullopt); } } - template<format_specifier spec> - static constexpr void format(const format_output auto& out, const std::remove_pointer_t<T>* t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void conversionSupported(format_specifier spec) { - static_assert(spec.conversion == 'p' || spec.conversion == 'v', "The given pointer type only supports the ‘p’ and ‘v’ conversions"); - return format(out, t, spec, minWidth, precision); + simpleConversionSpecifierCheck("pv", spec.conversion, "pointer type"); + noAlternative(spec.alternative, "pointer types"); + noSignFlags(spec.addSign, "pointer types"); + noPrecision(spec.precision, "pointer types"); + delegated_formatter::conversionSupported(delegateSpec); } }; template<> struct formatter<std::nullptr_t> { - static constexpr void format(const format_output auto& out, std::nullptr_t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void format(const format_output auto& out, std::nullptr_t, format_specifier spec, optional_int<std::size_t> minWidth, optional_int<std::size_t>) { formatPadded(out, "(nil)", ' ', spec.leftJustified, minWidth); } - template<format_specifier spec> - static constexpr void format(const format_output auto& out, std::nullptr_t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void conversionSupported(format_specifier spec) { - static_assert(spec.conversion == 'p' || spec.conversion == 'v', "The given pointer type only supports the ‘p’ and ‘v’ conversions"); - return format(out, nullptr, spec, minWidth, precision); + return formatter<void*>::conversionSupported(spec); } }; @@ -437,11 +480,11 @@ namespace format { } } - template<format_specifier spec> - static constexpr void format(const format_output auto& out, const char* s, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void conversionSupported(format_specifier spec) { - static_assert(spec.conversion == 'p' || spec.conversion == 's' || spec.conversion == 'v', "const char* only supports the ‘p’, ‘s’ and ‘v’ conversions"); - return format(out, s, spec, minWidth, precision); + simpleConversionSpecifierCheck("spv", spec.conversion, "char pointer"); + noAlternative(spec.alternative, "char pointer"); + noSignFlags(spec.addSign, "char pointer"); } }; @@ -472,16 +515,14 @@ namespace format { formatPadded(out, stream.view(), ' ', spec.leftJustified, minWidth); } - template<format_specifier spec> - static constexpr void format(const format_output auto& out, const T& t, optional_int<std::size_t> minWidth, optional_int<std::size_t> precision) + static constexpr void conversionSupported(format_specifier spec) { - static_assert(spec.conversion == 'v' || spec.conversion == 's', "The operator<<(std::ostream&) fallback only supports conversions ‘s’ and ‘v’"); - static_assert(!spec.precision, "The operator<<(std::ostream&) fallback does not support specifying any precision"); - static_assert(spec.addSign == '\0', "The operator<<(std::ostream&) fallback does not support the add sign (‘+‘) flag"); - static_assert(!spec.alternative, "The operator<<(std::ostream&) fallback does not support the alternative (‘#‘) flag"); - static_assert(spec.padding == ' ', "The operator<<(std::ostream&) fallback does not support padding with 0"); - static_assert(spec.length == format_specifier::Length::None, "The operator<<(std::ostream&) fallback does not support specifying any length"); - return format(out, t, spec, minWidth, precision); + constexpr std::string_view fallbackName{"operator<<(std::ostream&) fallback"}; + simpleConversionSpecifierCheck("sv", spec.conversion, fallbackName); + noAlternative(spec.alternative, fallbackName); + noSignFlags(spec.addSign, fallbackName); + noPrecision(spec.precision, fallbackName); + noZeroPadding(spec.padding, fallbackName); } }; } diff --git a/include/cxxformat/runtime.hpp b/include/cxxformat/runtime.hpp new file mode 100644 index 0000000..cc09b7e --- /dev/null +++ b/include/cxxformat/runtime.hpp @@ -0,0 +1,219 @@ +#pragma once + +#include <array> +#include <variant> +#include <vector> + +#include <cxxformat/core.hpp> +#include <cxxformat/formatters.hpp> +#include <cxxformat/string.hpp> + +namespace format { + namespace { + namespace run_time { + class format_template { + std::vector<std::variant<std::string, format_specifier>> parts; + std::size_t argCount; + + public: + format_template(std::vector<std::variant<std::string, format_specifier>>&& parts, std::size_t argCount) noexcept : parts{std::move(parts)}, argCount{argCount} {} + + template<format_output_compatible Output, typename... Args> requires (has_some_formatter<std::decay_t<Args>> && ...) + void operator()(Output&& output, Args&&... args) const + { + checkArgCount(sizeof...(Args)); + + std::array<optional_int<std::size_t>, sizeof...(Args)> indexArgs{[]<typename Arg>(Arg&& arg) constexpr -> optional_int<std::size_t> { + if constexpr (std::unsigned_integral<Arg> && sizeof(Arg) <= sizeof(std::size_t)) + { + return static_cast<std::size_t>(arg); + } + else + { + return std::nullopt; + } + }(std::forward<Args>(args))...}; + + const auto checkPart = [&args..., &indexArgs]<std::size_t... indices>(std::index_sequence<indices...>) + { + return [&args..., &indexArgs](format_specifier spec) + { + ([spec, &indexArgs]<typename Arg>(Arg&&, std::size_t index) + { + if (index == spec.argIndex) + { + if (spec.widthAsArg) + { + if (!indexArgs[*spec.minWidth]) + { + throw std::invalid_argument{"Width argument must be an unsigned integral at most as big as std::size_t"}; + } + } + if (spec.precisionAsArg) + { + if (!indexArgs[*spec.precision]) + { + throw std::invalid_argument{"Precision argument must be an unsigned integral at most as big as std::size_t"}; + } + } + formatter_with_fallback<std::decay_t<Arg>>::conversionSupported(spec); + } + }(std::forward<Args>(args), indices), ...); + }; + }(std::make_index_sequence<sizeof...(Args)>()); + + for (const auto& part : parts) + { + if (std::holds_alternative<format_specifier>(part)) + { + checkPart(std::get<format_specifier>(part)); + } + } + + const auto out = make_output(std::forward<Output>(output)); + const overloaded_callable visitor{ + [&args..., &out, &indexArgs]<std::size_t... indices>(std::index_sequence<indices...>) + { + return [&args..., &out, &indexArgs](format_specifier spec) + { + ([&out, spec, &indexArgs]<typename Arg>(Arg&& arg, std::size_t index) + { + if (index == spec.argIndex) + { + optional_int<std::size_t> minWidth = spec.minWidth; + if (spec.widthAsArg) + { + minWidth = indexArgs[*minWidth]; + assert(minWidth); + } + optional_int<std::size_t> precision = spec.precision; + if (spec.precisionAsArg) + { + precision = indexArgs[*precision]; + assert(precision); + } + return formatter_with_fallback<std::decay_t<Arg>>::format(out, std::forward<Arg>(arg), spec, minWidth, precision); + } + }(std::forward<Args>(args), indices), ...); + }; + }(std::make_index_sequence<sizeof...(Args)>()), + [&out](std::string text) { out(text); } + }; + for (const auto& part : parts) + { + std::visit(visitor, part); + } + } + + private: + void checkArgCount(std::size_t args) const + { + if (args > argCount) + { + throw std::invalid_argument{"Too many arguments passed!"}; + } + else if (args < argCount) + { + throw std::invalid_argument{"Not enough arguments passed!"}; + } + } + }; + + format_template parseFormat(std::string_view fmt) + { + ArgIndex nextArg{0}; + ArgIndex maxArg{0}; + + std::vector<std::variant<std::string, format_specifier>> parts; + + const auto addString = [&parts](std::string_view part) + { + if (!parts.empty() && std::holds_alternative<std::string>(parts.back())) + { + std::get<std::string>(parts.back()).append(part); + } + else + { + parts.emplace_back(std::string{part}); + } + }; + + for (std::size_t pos{0}; pos < fmt.size(); ) + { + const auto nextSpec = fmt.find('%', pos); + if (nextSpec == std::string_view::npos) + { + parts.emplace_back(std::string{fmt.substr(pos)}); + break; + } + + if (nextSpec + 1 >= fmt.size()) + { + throw std::invalid_argument{"Incomplete format specifier at end of format string"}; + } + + const auto specStart = fmt[nextSpec + 1]; + if (specStart == '%') + { + // + 1 to include the first % + addString(fmt.substr(pos, nextSpec - pos + 1)); + pos = nextSpec + 2; + } + else + { + const auto [newPos, spec, newNextArg, specMaxArg] = parseSpec(fmt, nextSpec + 1, nextArg); + if (!checkValidConversion(spec.conversion)) + { + throw std::invalid_argument{"Invalid conversion specifier ‘%c’ in format specifier “%s”"_format(spec.conversion, fmt.substr(nextSpec, newPos - nextSpec))}; + } + + addString(fmt.substr(pos, nextSpec - pos)); + + nextArg = newNextArg; + maxArg = std::max(maxArg, specMaxArg); + pos = newPos; + + parts.emplace_back(spec); + } + } + + return {std::move(parts), maxArg}; + } + } + } + + template<format_output_compatible Output, typename... Args> requires (has_some_formatter<std::decay_t<Args>> && ...) + void format_to(std::string_view fmt, Output&& output, Args&&... args) + { + run_time::parseFormat(fmt)(std::forward<Output>(output), std::forward<Args>(args)...); + } + + template<format_output_compatible Output, typename... Args> requires (has_some_formatter<std::decay_t<Args>> && ...) + void format_nothrow_to(std::string_view fmt, Output&& output, Args&&... args) noexcept + { + try + { + format_to(fmt, std::forward<Output>(output), std::forward<Args>(args)...); + } + catch(const std::exception& e) + { + "Formatting error: %s"_format_to(output, e.what()); + } + } + + template<typename... Args> requires (has_some_formatter<std::decay_t<Args>> && ...) + std::string format(std::string_view fmt, Args&&... args) + { + std::ostringstream out; + format_to(fmt, out, std::forward<Args>(args)...); + return out.str(); + } + + template<typename... Args> requires (has_some_formatter<std::decay_t<Args>> && ...) + std::string format_nothrow(std::string_view fmt, Args&&... args) + { + std::ostringstream out; + format_nothrow_to(fmt, out, std::forward<Args>(args)...); + return out.str(); + } +} @@ -18,6 +18,19 @@ void numberTests() int main(int argc, char* argv[]) { using namespace format; + using format::format; + + if (argc == 2) + { + std::string_view fmt{argv[1]}; + format_nothrow_to(fmt, stdout, "Knirp", 's', argv[0], 42u, 1.337); + putchar('\n'); + return 0; + } + + format_to("Hallo %%du %1$s %s%c %s %u = %4$#x = %4$#X = %4$#o %f!\n", std::cout, "Knirp", 's', argv[0], 42u, 1.337); + assert(format("Hallo %%du %1$s %s%c %s %u = %4$#x = %4$#X = %4$#o %f!\n", "Knirp", 's', argv[0], 42u, 1.337) == "Hallo %%du %1$s %s%c %s %u = %4$#x = %4$#X = %4$#o %f!\n"_format("Knirp", 's', argv[0], 42u, 1.337)); + "-%20g-\n"_format_to(stdout, 0x1.8p0); "-%20.0a-\n"_format_to(stdout, 0.0); "-%#+20.0a-\n"_format_to(stdout, 1.0); |
