/* * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "fud_algorithm.hpp" #include "fud_allocator.hpp" #include "fud_array.hpp" #include "fud_string.hpp" #include "fud_utf8.hpp" #include "fud_utf8_iterator.hpp" #include "fud_vector.hpp" #include "test_common.hpp" // #include "fud_format.hpp" // #include "fud_span.hpp" #include "gtest/gtest.h" namespace fud { constexpr size_t validAsciiSize = INT8_MAX + 1; constexpr size_t invalidAsciiSize = UINT8_MAX + 1 - validAsciiSize; constexpr size_t numControlChars = 33; constexpr char printableCharOffset = 0x20; constexpr auto invalidAscii = FudUtf8::invalidAsciiCode.character(); auto invalidAsciiGenerator() { return Iota{validAsciiSize, 1, invalidAsciiSize}; } template auto toUtf8(T letter) { return FudUtf8::make(static_cast(letter)); } template auto toLetter(T letter) { return static_cast(letter); } TEST(Utf8Test, Utf8Creation) { const Array threeByte = {THREE_BYTE}; FudUtf8 utf8Point{FudUtf8::make(threeByte)}; ASSERT_NE(utf8Point.data(), nullptr); ASSERT_EQ(utf8Point.size(), 3); ASSERT_NE(utf8Point.hash(), -1); const Array asciiLetter = {'A'}; utf8Point = FudUtf8::make(asciiLetter); ASSERT_NE(utf8Point.data(), nullptr); ASSERT_EQ(utf8Point.size(), 1); const Array twoByte = {TWO_BYTE}; utf8Point = FudUtf8::make(twoByte); ASSERT_NE(utf8Point.data(), nullptr); ASSERT_EQ(utf8Point.size(), 2); ASSERT_NE(utf8Point.hash(), -1); Array fourByte = { static_cast(FOUR_BYTE[0]), static_cast(FOUR_BYTE[1]), static_cast(FOUR_BYTE[2]), static_cast(FOUR_BYTE[3])}; utf8Point = FudUtf8::make(fourByte); ASSERT_NE(utf8Point.data(), nullptr); ASSERT_EQ(utf8Point.size(), 4); ASSERT_NE(utf8Point.hash(), -1); const Array invalidBytes = {0xFF, 0xFF, 0xFF, 0xFF}; utf8Point = FudUtf8::make(invalidBytes); ASSERT_EQ(utf8Point.data(), nullptr); ASSERT_EQ(utf8Point.size(), 0); ASSERT_EQ(utf8Point.hash(), -1); } TEST(Utf8Test, Utf8MultiByte) { Array data{MULTI_BYTE_LITERAL}; constexpr size_t bufSize = data.size(); EXPECT_EQ(data[bufSize - 1], '\0'); class FixedAllocator final : public Allocator { private: Array m_memory{}; size_t m_allocated{0}; public: virtual ~FixedAllocator() override final = default; virtual Result allocate(size_t bytes, size_t alignment) override final { static_cast(alignment); if (bytes > m_memory.size() - m_allocated) { return FudStatus::AllocFailure; } auto* data = m_memory.data() + m_allocated; m_allocated += bytes; return data; } virtual FudStatus deallocate(void* pointer, size_t bytes) override final { static_cast(pointer); static_cast(bytes); return FudStatus::Success; } virtual bool isEqual(const Allocator& rhs) const override final { return &rhs == this; } }; FixedAllocator fixedAllocator; auto stringBufferRes{String::makeFromCString(MULTI_BYTE_LITERAL, &fixedAllocator)}; ASSERT_TRUE(stringBufferRes.isOkay()); auto stringBuffer{stringBufferRes.takeOkay()}; EXPECT_EQ(stringBuffer.size(), bufSize); EXPECT_EQ(stringBuffer.size(), sizeof(data)); EXPECT_EQ(stringBuffer.length(), bufSize - 1); EXPECT_TRUE(stringBuffer.nullTerminated()); EXPECT_TRUE(stringBuffer.valid()); ASSERT_TRUE(stringBuffer.utf8Valid()); Utf8Iterator utf8Iter{stringBuffer}; auto characterOpt = utf8Iter.next(); ASSERT_TRUE(characterOpt.has_value()); // MULTI_BYTE_LITERAL "test今日素敵はですねƩ®😀z" const Array multiByteCharacters{ FudUtf8::make(Utf8Variant{Ascii{'t'}}), FudUtf8::make(Utf8Variant{Ascii{'e'}}), FudUtf8::make(Utf8Variant{Ascii{'s'}}), FudUtf8::make(Utf8Variant{Ascii{'t'}}), FudUtf8::from(StringView{sizeof("今"), "今"}, 0), FudUtf8::from(StringView{sizeof("日"), "日"}, 0), FudUtf8::from(StringView{sizeof("素"), "素"}, 0), FudUtf8::from(StringView{sizeof("敵"), "敵"}, 0), FudUtf8::from(StringView{sizeof("は"), "は"}, 0), FudUtf8::from(StringView{sizeof("で"), "で"}, 0), FudUtf8::from(StringView{sizeof("す"), "す"}, 0), FudUtf8::from(StringView{sizeof("ね"), "ね"}, 0), FudUtf8::from(StringView{sizeof("Ʃ"), "Ʃ"}, 0), FudUtf8::from(StringView{sizeof("®"), "®"}, 0), FudUtf8::from(StringView{sizeof("😀"), "😀"}, 0), FudUtf8::make(Utf8Variant{Ascii{'z'}}), }; size_t idx = 0; while (characterOpt.has_value()) { auto character = *characterOpt; if (character != FudUtf8{Utf8Variant{Ascii{'\0'}}}) { EXPECT_TRUE(character.size() >= 1); ASSERT_LT(idx, multiByteCharacters.size()); EXPECT_EQ(character.size(), multiByteCharacters[idx].size()); EXPECT_EQ(character, multiByteCharacters[idx]); EXPECT_TRUE(multiByteCharacters[idx].valid()); if (character != multiByteCharacters[idx]) { printf("idx = %zu, %.*s\n", idx, static_cast(character.size()), character.data()); } idx++; } characterOpt = utf8Iter.next(); } utf8Iter.reset(); ASSERT_TRUE(utf8Iter.next().has_value()); FudUtf8 invalid = FudUtf8::invalidAscii(); ASSERT_FALSE(invalid.valid()); ASSERT_EQ(invalid.size(), 0); ASSERT_EQ(invalid.data(), nullptr); ASSERT_EQ(invalid.hash(), -1); } TEST(Utf8Test, Utf8IsAscii) { ASSERT_FALSE(charIsAscii(invalidAscii)); Iota charIota{0, 1, validAsciiSize}; ASSERT_TRUE(allOf([&]() -> Option { return charIota().map(toLetter); }, charIsAscii)); Iota invalidCharIota{validAsciiSize, 1, invalidAsciiSize}; ASSERT_FALSE(anyOf([&]() -> Option { return invalidCharIota().map(toLetter); }, charIsAscii)); FudUtf8 unicode{FudUtf8::invalidAscii()}; ASSERT_FALSE(utf8IsAscii(unicode)); charIota.set(0); ASSERT_TRUE(allOf([&]() -> Option { return charIota().map(toUtf8); }, utf8IsAscii)); invalidCharIota.set(invalidAsciiSize); ASSERT_FALSE(anyOf([&]() -> Option { return invalidCharIota().map(toUtf8); }, utf8IsAscii)); } template struct SpanGenerator { Span span; size_t index{0}; void reset() { index = 0; } Option operator()() { if (index < span.size()) { index++; return span[index - 1]; } return NullOpt; } }; TEST(Utf8Test, Utf8IsAlphanumeric) { constexpr size_t numAlphanumericChars = 26 * 2 + 10; Array alphanumericCharLiteral{ALPHA_NUMERIC_CHARS}; Array alphanumericChars{}; copyMem(alphanumericChars, alphanumericCharLiteral); ASSERT_TRUE(allOf(alphanumericChars.span(), charIsAlphanumeric)); constexpr size_t numNonAlphanumericChars = validAsciiSize - numAlphanumericChars; Vector nonAlphanumericChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { if (!charIsAlphanumeric(idx)) { ASSERT_EQ(nonAlphanumericChars.pushBack(idx), FudStatus::Success); } } auto nonAlphanumericSpan{nonAlphanumericChars.span().takeOkay()}; ASSERT_FALSE(anyOf(nonAlphanumericSpan, charIsAlphanumeric)); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsAlphanumeric)); ASSERT_FALSE(utf8IsAlphanumeric(FudUtf8{Ascii{invalidAscii}})); auto iotaGenerator = invalidAsciiGenerator(); auto generator = generate( []() { return Array{}; }, [&]() { return iotaGenerator().map([](auto val) { return static_cast(val); }); }); SpanGenerator alphanumericGenerator{alphanumericChars.span()}; auto utf8AlphanumericGenerator = [&]() { return alphanumericGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8AlphanumericGenerator, utf8IsAlphanumeric)); SpanGenerator nonAlphanumericGenerator{nonAlphanumericChars.span().takeOkay()}; auto utf8NonAlphanumericGenerator = [&]() { return nonAlphanumericGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonAlphanumericGenerator, utf8IsAlphanumeric)); } TEST(Utf8Test, Utf8IsAlpha) { constexpr size_t numAlphaChars = sizeof(ALPHA_CHARS) - 1; Array alphaCharLiteral{ALPHA_CHARS}; Array alphaChars{}; copyMem(alphaChars, alphaCharLiteral); ASSERT_TRUE(allOf(alphaChars.span(), charIsAlpha)); constexpr size_t numNonAlphanumericChars = validAsciiSize - numAlphaChars; Vector nonAlphaChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { if (!charIsAlphanumeric(idx)) { ASSERT_EQ(nonAlphaChars.pushBack(idx), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonAlphaChars.span().takeOkay(), charIsAlpha)); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsAlpha)); ASSERT_FALSE(utf8IsAlpha(FudUtf8{Ascii{invalidAscii}})); SpanGenerator alphaGenerator{alphaChars.span()}; auto utf8AlphaGenerator = [&]() { return alphaGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8AlphaGenerator, utf8IsAlpha)); SpanGenerator nonAlphaGenerator{nonAlphaChars.span().takeOkay()}; auto utf8NonAlphaGenerator = [&]() { return nonAlphaGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonAlphaGenerator, utf8IsAlpha)); } TEST(Utf8Test, Utf8IsLower) { constexpr size_t numLowerChars = 26; Array lowerCharLiteral{LOWERCASE_CHARS}; Array lowerChars{}; copyMem(lowerChars, lowerCharLiteral); ASSERT_TRUE(allOf(lowerChars.span(), charIsLowercase)); constexpr size_t numNonLowerChars = validAsciiSize - numLowerChars; Vector nonLowerChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { if (!charIsLowercase(idx)) { ASSERT_EQ(nonLowerChars.pushBack(idx), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonLowerChars.span().takeOkay(), charIsLowercase)); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsLowercase)); ASSERT_FALSE(utf8IsLowercase(FudUtf8{Ascii{invalidAscii}})); SpanGenerator lowerGenerator{lowerChars.span()}; auto utf8LowerGenerator = [&]() { return lowerGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8LowerGenerator, utf8IsLowercase)); SpanGenerator nonLowerGenerator{nonLowerChars.span().takeOkay()}; auto utf8NonLowerGenerator = [&]() { return nonLowerGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonLowerGenerator, utf8IsLowercase)); } TEST(Utf8Test, Utf8IsUpper) { constexpr size_t numUpperChars = 26; Array upperCharLiteral{UPPERCASE_CHARS}; Array upperChars{}; copyMem(upperChars, upperCharLiteral); ASSERT_TRUE(allOf(upperChars.span(), charIsUppercase)); constexpr size_t numNonUpperChars = validAsciiSize - numUpperChars; Vector nonUpperChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { if (!charIsUppercase(idx)) { ASSERT_EQ(nonUpperChars.pushBack(idx), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonUpperChars.span().takeOkay(), charIsUppercase)); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsUppercase)); ASSERT_FALSE(utf8IsUppercase(FudUtf8{Ascii{invalidAscii}})); SpanGenerator upperGenerator{upperChars.span()}; auto utf8UpperGenerator = [&]() { return upperGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8UpperGenerator, utf8IsUppercase)); SpanGenerator nonUpperGenerator{nonUpperChars.span().takeOkay()}; auto utf8NonUpperGenerator = [&]() { return nonUpperGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonUpperGenerator, utf8IsUppercase)); } TEST(Utf8Test, Utf8IsDigit) { constexpr size_t numDigitChars = 10; Array digitCharLiteral{DECIMAL_CHARS}; Array digitChars{}; copyMem(digitChars, digitCharLiteral); ASSERT_TRUE(allOf(digitChars.span(), charIsDigit)); constexpr size_t numNonDigitChars = validAsciiSize - numDigitChars; Vector nonDigitChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { if (!charIsDigit(idx)) { ASSERT_EQ(nonDigitChars.pushBack(idx), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonDigitChars.span().takeOkay(), charIsDigit)); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsDigit)); ASSERT_FALSE(utf8IsDigit(FudUtf8{Ascii{invalidAscii}})); SpanGenerator digitGenerator{digitChars.span()}; auto utf8DigitGenerator = [&]() { return digitGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8DigitGenerator, utf8IsDigit)); SpanGenerator nonDigitGenerator{nonDigitChars.span().takeOkay()}; auto utf8NonDigitGenerator = [&]() { return nonDigitGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonDigitGenerator, utf8IsDigit)); } TEST(Utf8Test, Utf8IsHexDigit) { constexpr size_t numHexDigitChars = 6 * 2 + 10; Array hexDigitCharLiteral{"abcdefABCDEF0123456789"}; Array hexDigitChars{}; copyMem(hexDigitChars, hexDigitCharLiteral); ASSERT_TRUE(allOf(hexDigitChars.span(), charIsHexDigit)); constexpr size_t numNonHexDigitChars = validAsciiSize - numHexDigitChars; Vector nonHexDigitChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { if (!charIsHexDigit(idx)) { ASSERT_EQ(nonHexDigitChars.pushBack(idx), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonHexDigitChars.span().takeOkay(), charIsHexDigit)); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsHexDigit)); ASSERT_FALSE(utf8IsHexDigit(FudUtf8{Ascii{invalidAscii}})); SpanGenerator hexDigitGenerator{hexDigitChars.span()}; auto utf8HexDigitGenerator = [&]() { return hexDigitGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8HexDigitGenerator, utf8IsHexDigit)); SpanGenerator nonHexDigitGenerator{nonHexDigitChars.span().takeOkay()}; auto utf8NonHexDigitGenerator = [&]() { return nonHexDigitGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonHexDigitGenerator, utf8IsHexDigit)); } TEST(Utf8Test, Utf8IsControl) { Iota controlArrayGenerator{0, 1, numControlChars}; auto controlChars = generate([]() { return Array{}; }, controlArrayGenerator); constexpr const char deleteChar = 0x7F; controlChars.back() = deleteChar; ASSERT_TRUE(allOf(controlChars.span(), charIsControl)); constexpr size_t numNonControlChars = INT8_MAX + 1 - numControlChars; Vector nonControlChars{}; ASSERT_EQ(nonControlChars.reserve(numNonControlChars), FudStatus::Success); for (auto idx = numControlChars - 1; idx < deleteChar; ++idx) { ASSERT_EQ(nonControlChars.pushBack(idx), FudStatus::Success); } ASSERT_FALSE(anyOf(nonControlChars.span().takeOkay(), charIsControl)); ASSERT_TRUE(allOf(nonControlChars.span().takeOkay(), charIsAscii)); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsControl)); ASSERT_FALSE(utf8IsControl(FudUtf8{Ascii{invalidAscii}})); SpanGenerator controlGenerator{controlChars.span()}; auto utf8ControlGenerator = [&]() { return controlGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8ControlGenerator, utf8IsControl)); SpanGenerator nonControlGenerator{nonControlChars.span().takeOkay()}; auto utf8NonControlGenerator = [&]() { return nonControlGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonControlGenerator, utf8IsControl)); } TEST(Utf8Test, Utf8IsGraphical) { constexpr size_t numGraphicalChars = sizeof(GRAPHICAL_CHARS) - 1; Array graphicalCharLiteral{GRAPHICAL_CHARS}; Array graphicalChars{}; copyMem(graphicalChars, graphicalCharLiteral); ASSERT_TRUE(allOf(graphicalChars.span(), charIsGraphical)); constexpr size_t numNonGraphicalChars = validAsciiSize - numGraphicalChars; Vector nonGraphicalChars{}; ASSERT_EQ(nonGraphicalChars.reserve(numNonGraphicalChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { if (!charIsGraphical(static_cast(idx))) { ASSERT_EQ(nonGraphicalChars.pushBack(static_cast(idx)), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonGraphicalChars.span().takeOkay(), charIsGraphical)); ASSERT_TRUE(allOf(nonGraphicalChars.span().takeOkay(), charIsAscii)); ASSERT_EQ(nonGraphicalChars.size() + graphicalChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsGraphical)); ASSERT_FALSE(utf8IsGraphical(FudUtf8{Ascii{invalidAscii}})); SpanGenerator graphicalGenerator{graphicalChars.span()}; auto utf8GraphicalGenerator = [&]() { return graphicalGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8GraphicalGenerator, utf8IsGraphical)); SpanGenerator nonGraphicalGenerator{nonGraphicalChars.span().takeOkay()}; auto utf8NonGraphicalGenerator = [&]() { return nonGraphicalGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonGraphicalGenerator, utf8IsGraphical)); } TEST(Utf8Test, Utf8IsSpace) { constexpr size_t numSpaceChars = sizeof(SPACE_CHARS) - 1; Array spaceCharLiteral{SPACE_CHARS}; Array spaceChars{}; copyMem(spaceChars, spaceCharLiteral); ASSERT_TRUE(allOf(spaceChars.span(), charIsSpace)); constexpr size_t numNonSpaceChars = validAsciiSize - numSpaceChars; Vector nonSpaceChars{}; ASSERT_EQ(nonSpaceChars.reserve(numNonSpaceChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { if (!charIsSpace(static_cast(idx))) { ASSERT_EQ(nonSpaceChars.pushBack(static_cast(idx)), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonSpaceChars.span().takeOkay(), charIsSpace)); ASSERT_TRUE(allOf(nonSpaceChars.span().takeOkay(), charIsAscii)); ASSERT_EQ(nonSpaceChars.size() + spaceChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsSpace)); ASSERT_FALSE(utf8IsSpace(FudUtf8{Ascii{invalidAscii}})); SpanGenerator spaceGenerator{spaceChars.span()}; auto utf8SpaceGenerator = [&]() { return spaceGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8SpaceGenerator, utf8IsSpace)); SpanGenerator nonSpaceGenerator{nonSpaceChars.span().takeOkay()}; auto utf8NonSpaceGenerator = [&]() { return nonSpaceGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonSpaceGenerator, utf8IsSpace)); } TEST(Utf8Test, Utf8IsBlank) { constexpr size_t numBlankChars = sizeof(BLANK_CHARS) - 1; Array blankCharLiteral{BLANK_CHARS}; Array blankChars{}; copyMem(blankChars, blankCharLiteral); ASSERT_TRUE(allOf(blankChars.span(), charIsBlank)); constexpr size_t numNonBlankChars = validAsciiSize - numBlankChars; Vector nonBlankChars{}; ASSERT_EQ(nonBlankChars.reserve(numNonBlankChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { if (!charIsBlank(static_cast(idx))) { ASSERT_EQ(nonBlankChars.pushBack(static_cast(idx)), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonBlankChars.span().takeOkay(), charIsBlank)); ASSERT_TRUE(allOf(nonBlankChars.span().takeOkay(), charIsAscii)); ASSERT_EQ(nonBlankChars.size() + blankChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsBlank)); ASSERT_FALSE(utf8IsBlank(FudUtf8{Ascii{invalidAscii}})); SpanGenerator blankGenerator{blankChars.span()}; auto utf8BlankGenerator = [&]() { return blankGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8BlankGenerator, utf8IsBlank)); SpanGenerator nonBlankGenerator{nonBlankChars.span().takeOkay()}; auto utf8NonBlankGenerator = [&]() { return nonBlankGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonBlankGenerator, utf8IsBlank)); } TEST(Utf8Test, Utf8IsPrintable) { constexpr size_t numPrintableChars = sizeof(PRINTABLE_CHARS) - 1; Array printableCharLiteral{PRINTABLE_CHARS}; Array printableChars{}; copyMem(printableChars, printableCharLiteral); ASSERT_TRUE(allOf(printableChars.span(), charIsPrintable)); constexpr size_t numNonPrintableChars = validAsciiSize - numPrintableChars; Vector nonPrintableChars{}; ASSERT_EQ(nonPrintableChars.reserve(numNonPrintableChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { if (!charIsPrintable(static_cast(idx))) { ASSERT_EQ(nonPrintableChars.pushBack(static_cast(idx)), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonPrintableChars.span().takeOkay(), charIsPrintable)); ASSERT_TRUE(allOf(nonPrintableChars.span().takeOkay(), charIsAscii)); ASSERT_EQ(nonPrintableChars.size() + printableChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsPrintable)); ASSERT_FALSE(utf8IsPrintable(FudUtf8{Ascii{invalidAscii}})); SpanGenerator printableGenerator{printableChars.span()}; auto utf8PrintableGenerator = [&]() { return printableGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8PrintableGenerator, utf8IsPrintable)); SpanGenerator nonPrintableGenerator{nonPrintableChars.span().takeOkay()}; auto utf8NonPrintableGenerator = [&]() { return nonPrintableGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonPrintableGenerator, utf8IsPrintable)); } TEST(Utf8Test, Utf8IsPunctuation) { constexpr size_t numPunctuationChars = sizeof(PUNCTUATION_CHARS) - 1; Array punctuationCharLiteral{PUNCTUATION_CHARS}; Array punctuationChars{}; copyMem(punctuationChars, punctuationCharLiteral); ASSERT_TRUE(allOf(punctuationChars.span(), charIsPunctuation)); constexpr size_t numNonPunctuationChars = validAsciiSize - numPunctuationChars; Vector nonPunctuationChars{}; ASSERT_EQ(nonPunctuationChars.reserve(numNonPunctuationChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { if (!charIsPunctuation(static_cast(idx))) { ASSERT_EQ(nonPunctuationChars.pushBack(static_cast(idx)), FudStatus::Success); } } ASSERT_FALSE(anyOf(nonPunctuationChars.span().takeOkay(), charIsPunctuation)); ASSERT_TRUE(allOf(nonPunctuationChars.span().takeOkay(), charIsAscii)); ASSERT_EQ(nonPunctuationChars.size() + punctuationChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); ASSERT_FALSE(anyOf(invalidAsciiChars, charIsPunctuation)); ASSERT_FALSE(utf8IsPunctuation(FudUtf8{Ascii{invalidAscii}})); SpanGenerator punctuationGenerator{punctuationChars.span()}; auto utf8PunctuationGenerator = [&]() { return punctuationGenerator().map(toUtf8); }; ASSERT_TRUE(allOf(utf8PunctuationGenerator, utf8IsPunctuation)); SpanGenerator nonPunctuationGenerator{nonPunctuationChars.span().takeOkay()}; auto utf8NonPunctuationGenerator = [&]() { return nonPunctuationGenerator().map(toUtf8); }; ASSERT_FALSE(anyOf(utf8NonPunctuationGenerator, utf8IsPunctuation)); } } // namespace fud