93namespace morph::render {
149 auto const lead =
static_cast<unsigned char>(text.front());
151 return {.value = lead, .length = 1};
154 std::size_t length = 0;
157 if ((lead & 0xE0U) == 0xC0U) {
159 value = lead & 0x1FU;
161 }
else if ((lead & 0xF0U) == 0xE0U) {
163 value = lead & 0x0FU;
165 }
else if ((lead & 0xF8U) == 0xF0U) {
167 value = lead & 0x07U;
172 if (text.size() < length) {
175 for (std::size_t k = 1; k < length; ++k) {
178 auto const cont =
static_cast<unsigned char>(text[k]);
179 if ((cont & 0xC0U) != 0x80U) {
182 value = (value << 6U) | (cont & 0x3FU);
184 constexpr char32_t kMaxScalar = 0x10FFFF;
185 constexpr char32_t kSurrogateFirst = 0xD800;
186 constexpr char32_t kSurrogateLast = 0xDFFF;
187 if (value < least || value > kMaxScalar || (value >= kSurrogateFirst && value <= kSurrogateLast)) {
190 return {.value = value, .length = length};
198 constexpr char32_t kContMask = 0x3F;
199 constexpr unsigned kContShift = 6;
200 auto const byte = [&out](
char32_t bits) { out +=
static_cast<char>(bits); };
203 }
else if (value < 0x800) {
204 byte(0xC0U | (value >> kContShift));
205 byte(0x80U | (value & kContMask));
206 }
else if (value < 0x10000) {
207 byte(0xE0U | (value >> (2 * kContShift)));
208 byte(0x80U | ((value >> kContShift) & kContMask));
209 byte(0x80U | (value & kContMask));
211 byte(0xF0U | (value >> (3 * kContShift)));
212 byte(0x80U | ((value >> (2 * kContShift)) & kContMask));
213 byte(0x80U | ((value >> kContShift) & kContMask));
214 byte(0x80U | (value & kContMask));
230 if (chr >=
'0' && chr <=
'9') {
231 appendUtf8(out, base +
static_cast<char32_t>(chr -
'0'));
247 if (value < 0x10000) {
263[[nodiscard]]
inline char32_t digitBase(std::string_view zeroDigit) {
292 if (decoded.
length == 0) {
295 if (decoded.
value >= base && decoded.
value < base + 10) {
297 .length = decoded.
length, .canonical =
static_cast<char>(U
'0' + (decoded.
value - base)), .native =
true};
299 if (decoded.
value >= U
'0' && decoded.
value <= U
'9') {
303 return {.length = decoded.
length, .canonical =
static_cast<char>(decoded.
value), .native =
false};
342 constexpr std::size_t kGroupSize = 3;
344 std::size_t digits = 0;
345 bool sawGroup =
false;
346 bool sawDecimal =
false;
348 for (std::size_t i = 0; i < text.size();) {
349 const std::string_view rest = text.substr(i);
351 bool const opensAGroup = sawGroup ? digits == kGroupSize : (digits >= 1 && digits <= kGroupSize);
352 if (sawDecimal || !opensAGroup) {
361 if (sawGroup && digits != kGroupSize) {
370 digits = digit.
length != 0 ? digits + 1 : 0;
376 return !sawGroup || sawDecimal || digits == kGroupSize;
393 return !separator.empty() && rest.starts_with(separator) ? separator.size() : 0U;
418[[nodiscard]]
inline std::size_t
leadingSignLength(std::string_view rest, std::string_view localeSign,
420 if (!localeSign.empty() && rest.starts_with(localeSign)) {
421 return localeSign.size();
423 return rest.starts_with(asciiSign) ? 1U : 0U;
457 if (negativeLength != 0) {
459 return {.length = negativeLength, .emits =
"-"};
599 std::string canonical;
600 canonical.reserve(text.size());
601 bool sawDecimal =
false;
602 bool sawAnyOutput =
false;
603 bool sawNativeDigit =
false;
604 bool sawAsciiDigit =
false;
606 for (std::size_t i = 0; i < text.size();) {
607 const std::string_view rest = text.substr(i);
641 canonical += sign.
emits;
655 (digit.
native ? sawNativeDigit : sawAsciiDigit) =
true;
661 if (sawNativeDigit && sawAsciiDigit) {
664 if (canonical.empty() || canonical ==
"-") {
709 constexpr std::size_t kGroupSize = 3;
711 bool const neg = !canonicalText.empty() && canonicalText.front() ==
'-';
712 std::string_view
const magnitude = neg ? canonicalText.substr(1) : canonicalText;
713 auto const dot = magnitude.find(
'.');
714 std::string_view
const wholePart = dot == std::string_view::npos ? magnitude : magnitude.substr(0, dot);
715 std::string_view
const fracPart = dot == std::string_view::npos ? std::string_view{} : magnitude.substr(dot + 1);
721 out.reserve(loc.
negativeSign.size() + (magnitude.size() * perDigit) +
727 std::size_t index = 0;
728 for (
char const chr : wholePart) {
729 if (!loc.
groupSeparator.empty() && index != 0 && (wholePart.size() - index) % kGroupSize == 0) {
735 if (!fracPart.empty()) {
737 for (
char const chr : fracPart) {
759 std::string_view text,
NumericLocale const& loc, std::optional<std::uint32_t> maxDecimalPlaces = std::nullopt) {
761 if (!canonical.has_value()) {
764 std::string_view rest{*canonical};
765 bool const negative = rest.starts_with(
'-');
767 rest.remove_prefix(1);
769 auto const dot = rest.find(
'.');
770 std::string_view
const whole = rest.substr(0, dot);
771 std::string_view
const fraction = dot == std::string_view::npos ? std::string_view{} : rest.substr(dot + 1);
772 auto const allDigits = [](std::string_view part) {
773 return !part.empty() && part.find_first_not_of(
"0123456789") == std::string_view::npos;
775 if (!allDigits(whole) || (dot != std::string_view::npos && !allDigits(fraction))) {
778 auto const places =
static_cast<std::uint32_t
>(fraction.size());
782 constexpr auto kLimit =
static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max());
783 std::uint64_t magnitude = 0;
784 for (std::string_view
const part : {whole, fraction}) {
785 for (
char const chr : part) {
786 auto const digit =
static_cast<std::uint64_t
>(chr -
'0');
787 if (magnitude > (kLimit - digit) / 10U) {
790 magnitude = (magnitude * 10U) + digit;
793 auto const signedMagnitude =
static_cast<std::int64_t
>(magnitude);
Exact rational arithmetic for morph model/action values.
constexpr std::uint32_t kMaxDecimalPlaces
Largest decimal precision the type supports: 10^18 is the greatest power of ten that fits in std::int...
Strong type for a decimal-precision count.
Strong type for a Rational denominator.
Strong type for a Rational numerator.
Exact rational number with a runtime decimal-precision tag.
The locale facts both control-edge conversions need, in one designated-initialisable aggregate.
std::string_view groupSeparator
std::string_view decimalSeparator
std::string_view zeroDigit
std::string_view positiveSign
std::string_view negativeSign
One decoded UTF-8 code point: its value and how many bytes it took.
char32_t value
The decoded scalar value; meaningless when length is 0.
A digit matched at the start of an entry.
char canonical
The canonical ASCII spelling of the digit's value, '0'-'9'.
std::size_t length
The number of bytes the digit occupies; 0 when there is no digit there.
A sign matched at the start of an entry: how much of the text it occupies, and what it contributes to...
std::size_t length
The number of bytes the sign occupies; 0 when there is no sign there.