summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/cxxformat/core.hpp32
-rw-r--r--include/cxxformat/formatters.hpp161
-rw-r--r--main.cpp20
3 files changed, 186 insertions, 27 deletions
diff --git a/include/cxxformat/core.hpp b/include/cxxformat/core.hpp
index acf8613..fdddece 100644
--- a/include/cxxformat/core.hpp
+++ b/include/cxxformat/core.hpp
@@ -137,12 +137,15 @@ namespace format {
char conversion;
char addSign;
char padding;
+ Length length;
struct Flags {
- bool leftJustified : 1;
- bool alternative : 1;
- bool widthAsArg : 1;
- bool precisionAsArg : 1;
- Length length : 4;
+ // following after % or %<n>$
+ bool leftJustified : 1; // -
+ bool alternative : 1; // #
+ bool quote : 1; // '
+
+ bool widthAsArg : 1; // if width is *
+ bool precisionAsArg : 1; // if precision is *
} flags;
};
}
@@ -263,12 +266,13 @@ namespace format {
.precision = std::nullopt,
.addSign = '\0',
.padding = ' ',
+ .length = format_specifier::None,
.flags = {
.leftJustified = false,
.alternative = false,
+ .quote = false,
.widthAsArg = false,
.precisionAsArg = false,
- .length = format_specifier::None
}
};
@@ -313,6 +317,10 @@ namespace format {
{
spec.padding = '0';
}
+ else if (c == '\'')
+ {
+ spec.flags.quote = true;
+ }
else
{
break;
@@ -389,33 +397,33 @@ namespace format {
{
++pos;
checkEos();
- spec.flags.length = (c == 'h' ? L::Quarter : L::LongLong);
+ spec.length = (c == 'h' ? L::Quarter : L::LongLong);
++pos;
}
else
{
- spec.flags.length = (c == 'h' ? L::Half : L::Long);
+ spec.length = (c == 'h' ? L::Half : L::Long);
++pos;
}
}
else if (c == 'L')
{
- spec.flags.length = L::LongDouble;
+ spec.length = L::LongDouble;
++pos;
}
else if (c == 'j')
{
- spec.flags.length = L::Maximum;
+ spec.length = L::Maximum;
++pos;
}
else if (c == 'z')
{
- spec.flags.length = L::SizeT;
+ spec.length = L::SizeT;
++pos;
}
else if (c == 't')
{
- spec.flags.length = L::PtrDiff;
+ spec.length = L::PtrDiff;
++pos;
}
}
diff --git a/include/cxxformat/formatters.hpp b/include/cxxformat/formatters.hpp
index b248b17..b9d6fcc 100644
--- a/include/cxxformat/formatters.hpp
+++ b/include/cxxformat/formatters.hpp
@@ -36,6 +36,58 @@ namespace format {
}
}
+ constexpr void formatQuoteGrouped(const format_output auto& out, std::string_view subject, std::size_t leadingZeros, unsigned int grouping)
+ {
+ const auto quote = '\'';
+ const auto subjectDigits = subject.size();
+ const auto totalDigits = subjectDigits + leadingZeros;
+ const auto firstZeros = totalDigits % grouping;
+ const auto firstSubject = subjectDigits % grouping;
+
+ if (firstZeros > 0 && leadingZeros >= firstZeros)
+ {
+ out('0', firstZeros);
+ out(quote);
+ leadingZeros -= firstZeros;
+ }
+
+ for (; leadingZeros > grouping; leadingZeros -= grouping)
+ {
+ out('0', grouping);
+ out(quote);
+ }
+
+ out('0', leadingZeros);
+
+ auto subjectPos = 0;
+ if (firstSubject > 0 && subjectDigits > firstSubject)
+ {
+ out(subject.substr(0, firstSubject));
+ subjectPos += firstSubject;
+ out(quote);
+ }
+
+ for (; subjectPos + grouping < subjectDigits; subjectPos += grouping)
+ {
+ out(subject.substr(subjectPos, grouping));
+ out(quote);
+ }
+
+ out(subject.substr(subjectPos));
+ }
+
+ constexpr std::size_t calculateNeededGroupedZeroPadding(std::size_t targetWidth, std::size_t digitCount, unsigned int grouping) noexcept
+ {
+ // tw = targetWidth; d = digitCount; g = grouping; x = result
+ // tw = (d + x) / g - 1 + d + x
+ // tw = (d + d * g + x + x * g + - g) / g
+ // tw * g = (d + d * g + x + x * g - g)
+ // tw * g - (d + d * g - g) = x * (1 + g)
+ // ((tw - (- 1)) * g - (d * (1 + g))) / (1 + g) = x
+ // ((tw + 1) * g - (d * (1 + g))) / (1 + g) = x
+ return ((targetWidth + 1) * grouping - (digitCount * (1 + grouping))) / (1 + grouping);
+ }
+
constexpr void simpleConversionSpecifierCheck(std::string_view supported, char c, std::string_view typeDesc)
{
using namespace std::string_literals;
@@ -72,6 +124,15 @@ namespace format {
}
}
+ constexpr void noQuote(bool quote, std::string_view typeDesc)
+ {
+ using namespace std::string_literals;
+ if (quote)
+ {
+ throw std::invalid_argument{"The quote (‘'’) flag is not supported for "s + std::string{typeDesc}};
+ }
+ }
+
constexpr void noPrecision(optional_int<std::size_t> precision, std::string_view typeDesc)
{
using namespace std::string_literals;
@@ -80,6 +141,16 @@ namespace format {
throw std::invalid_argument{"Specifying a precision is not supported for "s + std::string{typeDesc}};
}
}
+
+ template<std::integral T, std::integral U>
+ constexpr auto ceilDiv(T nom_, U den_)
+ {
+ using Result = std::common_type_t<T, U>;
+ Result nom = static_cast<Result>(nom_);
+ Result den = static_cast<Result>(den_);
+
+ return (nom + den - 1) / den;
+ }
}
using invalid_argument = std::invalid_argument;
@@ -97,6 +168,7 @@ namespace format {
}
const int base = std::unsigned_integral<T> ? (conv == 'o' ? 8 : "xX"_contains(conv) ? 16 : 10) : 10;
+ const unsigned int quoteGrouping = spec.flags.quote ? (base == 10 ? 3 : 2) : 0;
std::string_view converted;
std::array<char, (std::numeric_limits<T>::digits + 2) / 3> result;
@@ -162,19 +234,31 @@ namespace format {
precisionPadding = *precision - converted.size();
}
}
- else if (minWidth && (spec.flags.alternative && base != 10 && spec.padding == '0'))
+ else if (minWidth && spec.padding == '0')
{
- const auto totalLength = converted.size() + prefix.size();
+ const auto numSize = converted.size();
+ const auto restSize = prefix.size();
+ const auto totalLength = numSize + restSize + (quoteGrouping != 0 ? numSize / quoteGrouping : 0);
if (*minWidth > totalLength)
{
- precisionPadding = *minWidth - totalLength;
- minWidth.clear();
+ if (quoteGrouping != 0)
+ {
+ precisionPadding = calculateNeededGroupedZeroPadding(*minWidth - restSize, numSize, quoteGrouping);
+ spec.padding = ' ';
+ }
+ else
+ {
+ precisionPadding = *minWidth - totalLength;
+ minWidth.clear();
+ }
}
}
if (minWidth)
{
- const auto totalLength = converted.size() + precisionPadding + prefix.size();
+ const auto numSize = converted.size();
+ const auto quoteSize = quoteGrouping != 0 ? ((numSize + precisionPadding - 1) / quoteGrouping) : 0;
+ const auto totalLength = numSize + precisionPadding + quoteSize + prefix.size();
if (*minWidth > totalLength)
{
normalPadding = *minWidth - totalLength;
@@ -188,9 +272,16 @@ namespace format {
}
out(prefix);
- out('0', precisionPadding);
- out(converted);
+ if (quoteGrouping != 0)
+ {
+ formatQuoteGrouped(out, converted, precisionPadding, quoteGrouping);
+ }
+ else
+ {
+ out('0', precisionPadding);
+ out(converted);
+ }
if(spec.flags.leftJustified)
{
@@ -226,6 +317,11 @@ namespace format {
noPrecision(spec.precision, "integral types with %c");
}
+ if ("co"_contains(conv))
+ {
+ noQuote(spec.flags.quote, "integral types with %c and %o");
+ }
+
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"};
@@ -262,6 +358,7 @@ namespace format {
simpleConversionSpecifierCheck("usv", conv, "bool");
noAlternative(spec.flags.alternative, "bools");
noSignFlags(spec.addSign, "bools");
+ noQuote(spec.flags.quote, "bools");
if (conv == 'u')
{
@@ -312,6 +409,8 @@ namespace format {
assert(!"Unsupported conversion; should have been caught by formatter<T>::conversionSupported");
}
+ const unsigned int quoteGrouping = spec.flags.quote ? (format == std::chars_format::hex ? 2 : 3) : 0;
+
optional_int<int> intPrecision;
if (precision)
@@ -365,8 +464,15 @@ namespace format {
mantissa = converted.substr(0, exponentPos);
exponent = converted.substr(exponentPos);
}
+ std::string_view integralPart = mantissa;
+ std::string_view fractionalPart;
+ if (const auto dotPos = mantissa.find('.'); dotPos != std::string_view::npos)
+ {
+ integralPart = mantissa.substr(0, dotPos);
+ fractionalPart = mantissa.substr(dotPos);
+ }
- const bool hasDot = mantissa.find('.') != std::string_view::npos;
+ const bool hasDot = fractionalPart.starts_with('.');
const bool addDot = spec.flags.alternative && !hasDot;
std::size_t extraZeros{0};
@@ -401,13 +507,32 @@ namespace format {
}
}
+
+ std::size_t zeroPadding = 0;
std::size_t paddingLength{0};
if (minWidth)
{
- const auto totalLength = (signPrefix != '\0' ? 1 : 0) + hexPrefix.size() + converted.size() + (addDot ? 1 : 0) + extraZeros;
+ const auto prefixSize = (signPrefix != '\0' ? 1 : 0) + hexPrefix.size();
+ const auto dotSize = (addDot ? 1 : 0);
+ const auto integralDigitCount = integralPart.size();
+ const auto convertedSize = converted.size();
+ const auto totalLength = prefixSize + convertedSize + dotSize + extraZeros + (quoteGrouping != 0 ? integralDigitCount / quoteGrouping : 0);
if (*minWidth > totalLength)
{
- paddingLength = *minWidth - totalLength;
+ if (quoteGrouping != 0 && spec.padding == '0')
+ {
+ zeroPadding = calculateNeededGroupedZeroPadding(*minWidth - prefixSize - dotSize - (convertedSize - integralDigitCount), integralDigitCount, quoteGrouping);
+ spec.padding = ' ';
+ paddingLength = *minWidth - (totalLength + zeroPadding + zeroPadding / quoteGrouping);
+ }
+ else
+ {
+ paddingLength = *minWidth - totalLength;
+ if(!spec.flags.leftJustified && spec.padding == '0')
+ {
+ zeroPadding = paddingLength;
+ }
+ }
}
}
@@ -422,12 +547,16 @@ namespace format {
}
out(hexPrefix);
- if(!spec.flags.leftJustified && spec.padding == '0')
+ if (quoteGrouping != 0)
{
- out(spec.padding, paddingLength);
+ formatQuoteGrouped(out, integralPart, zeroPadding, quoteGrouping);
+ out(fractionalPart);
+ }
+ else
+ {
+ out('0', zeroPadding);
+ out(mantissa);
}
-
- out(mantissa);
if (addDot)
{
out('.');
@@ -470,6 +599,7 @@ namespace format {
noAlternative(spec.flags.alternative, "stringy types");
noSignFlags(spec.addSign, "stringy types");
noZeroPadding(spec.padding, "stringy types");
+ noQuote(spec.flags.quote, "stringy types");
}
};
@@ -500,6 +630,7 @@ namespace format {
noSignFlags(spec.addSign, "pointer types");
noPrecision(spec.precision, "pointer types");
noZeroPadding(spec.padding, "pointer types");
+ noQuote(spec.flags.quote, "pointer types");
delegated_formatter::conversionSupported(delegateSpec);
}
};
@@ -546,6 +677,7 @@ namespace format {
noAlternative(spec.flags.alternative, "char pointer");
noSignFlags(spec.addSign, "char pointer");
noZeroPadding(spec.padding, "char pointer");
+ noQuote(spec.flags.quote, "char pointer");
}
};
@@ -584,6 +716,7 @@ namespace format {
noSignFlags(spec.addSign, fallbackName);
noPrecision(spec.precision, fallbackName);
noZeroPadding(spec.padding, fallbackName);
+ noQuote(spec.flags.quote, fallbackName);
}
};
}
diff --git a/main.cpp b/main.cpp
index ff4a4a0..91b5aa1 100644
--- a/main.cpp
+++ b/main.cpp
@@ -30,7 +30,25 @@ int main(int argc, char* argv[])
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));
- "-%5s-%5s-%u-%u-\n"_format_to(stdout, true, false, true, false);
+ "-%6s--%6s--%6u--%6u-\n"_format_to(stdout, true, false, true, false);
+ "-%'030f-\n"_format_to(stdout, 1'000'000.337);
+ "-%'030a-\n"_format_to(stdout, 1'000'000.337);
+ "-%'030g-\n"_format_to(stdout, 1'000'000.337);
+ "-%'030e-\n"_format_to(stdout, 1'000'000.337);
+ "-%'30f-\n"_format_to(stdout, 1'000'000.337);
+ "-%'20x-\n"_format_to(stdout, 1'000'000u);
+ "-%'20u-\n"_format_to(stdout, 1'000'000u);
+ "- %'19x-\n"_format_to(stdout, 1'000'000u);
+ "- %'19u-\n"_format_to(stdout, 1'000'000u);
+ "- %'018x-\n"_format_to(stdout, 1'000'000u);
+ "- %'018u-\n"_format_to(stdout, 1'000'000u);
+ "- %'18x-\n"_format_to(stdout, 1'000'000u);
+ "- %'18u-\n"_format_to(stdout, 1'000'000u);
+ "- %'019x-\n"_format_to(stdout, 1'000'000u);
+ "- %'019u-\n"_format_to(stdout, 1'000'000u);
+ "-%'020x-\n"_format_to(stdout, 1'000'000u);
+ "-%'020u-\n"_format_to(stdout, 1'000'000u);
+ "-%'20.10u-\n"_format_to(stdout, 1'000'000u);
"-%20g-\n"_format_to(stdout, 0x1.8p0);
"-%20.0a-\n"_format_to(stdout, 0.0);
"-%#+20.0a-\n"_format_to(stdout, 1.0);