Skip to content

Commit

Permalink
More tests and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
gershnik committed Jan 18, 2024
1 parent dfdab4d commit 4a95cd5
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 6 deletions.
2 changes: 1 addition & 1 deletion include/objc-helpers/BoxUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ namespace BoxMakerDetail __attribute__((visibility("hidden"))) {
template<class T>
class __attribute__((visibility("hidden"))) BoxMaker {
private:
static auto detectBoxedType() {
static consteval auto detectBoxedType() {
if constexpr (std::totally_ordered<T>) {
if constexpr (std::is_copy_constructible_v<T>) {
return (NSObject<BoxedValue, BoxedComparable, NSCopying> *)nullptr;
Expand Down
119 changes: 114 additions & 5 deletions include/objc-helpers/NSStringUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,17 +368,33 @@ class NSStringCharAccess
CFIndex _size = 0;
};

/**
Converts CFStringRef to `std::basic_string<Char>`
@tparam Char character type of the resulatant std::basic_string
@param str string to convert. If nullptr an empty string is returned
@param start start index. If less than 0 assumed to be 0. If greater than length of the string an empty string is returned.
@param length number of source characters to convert. If less than 0 or start + length exceeds the length of the string assumed "to the end of string".
Convertions to char16_t are exact and never fail. Conversions to other character types are transcodings and can fail if source string contains invalid UTF-16
sequences. In such cases an empty string is returned. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char>
auto makeStdString(CFStringRef __nullable str, CFIndex start = 0, CFIndex length = -1) {
using RetType = std::basic_string<Char>;

if (!str)
return RetType();
auto strLength = CFStringGetLength(str);
if (start >= strLength)
return RetType();
if (start < 0)
start = 0;
if (length < 0) {
length = CFStringGetLength(str);
length -= std::min(length, start);
}
if (length < 0)
length = strLength - start;
else
length = std::min(length, strLength - start);

if constexpr (std::is_same_v<Char, char16_t>) {
RetType ret(size_t(length), '\0');
CFStringGetCharacters(str, {start, length}, (UniChar *)ret.data());
Expand All @@ -397,25 +413,68 @@ auto makeStdString(CFStringRef __nullable str, CFIndex start = 0, CFIndex length

#ifdef __OBJC__

/**
Converts NSString to `std::basic_string<Char>`
@tparam Char character type of the resulatant std::basic_string
@param str string to convert. If nil an empty string is returned
@param location start index. If greater than length of the string an empty string is returned.
@param length number of source characters to convert. If start + length exceeds the length of the string assumed "to the end of string".
Convertions to char16_t are exact and never fail. Conversions to other character types are transcodings and can fail if source string contains invalid UTF-16
sequences. In such cases an empty string is returned. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char>
inline auto makeStdString(NSString * __nullable str, NSUInteger location = 0, NSUInteger length = NSUInteger(-1)) {
return makeStdString<Char>((__bridge CFStringRef)str, CFIndex(location), CFIndex(length));
}

#endif

/**
Converts a range denoted by a pair of NSStringCharAccess::const_iterator to `std::basic_string<Char>`
@tparam Char character type of the resulatant std::basic_string
If iterators do not form a valid substring of some NSString the behavior is undefined. If first and alst are both uninitialized iterators the result is an empty string.
Convertions to char16_t are exact and never fail. Conversions to other character types are transcodings and can fail if source string contains invalid UTF-16
sequences. In such cases an empty string is returned. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char>
inline auto makeStdString(NSStringCharAccess::const_iterator first, NSStringCharAccess::const_iterator last) {
return makeStdString<Char>(first.getCFString(), first.index(), last.index() - first.index());
}


/**
Converts NSStringCharAccess or a view derived from it to `std::basic_string<Char>`
@tparam Char character type of the resulatant std::basic_string
A dervied view must produce the original view iterators. Thus, for example, `std::views::take(N) would work while
`std::views::reverse` will not (and won't compile) since the latter's iterators are not the same as NSStringCharAccess ones.
If iterators do not form a valid substring of some NSString the behavior is undefined. If first and alst are both uninitialized iterators the result is an empty string.
Convertions to char16_t are exact and never fail. Conversions to other character types are transcodings and can fail if source string contains invalid UTF-16
sequences. In such cases an empty string is returned. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char, std::ranges::common_range Range>
requires(std::is_same_v<std::ranges::iterator_t<std::remove_cvref_t<Range>>, NSStringCharAccess::const_iterator>)
inline auto makeStdString(Range && range) {
return makeStdString<Char>(std::ranges::begin(range), std::ranges::end(range));
}

/**
Converts any contiguous range of characters to CFString
@returns nullptr on failure
The type of range's characters can be any of: char, char16_t, char32_t, char8_t, wchar_t.
Convertions from char16_t are exact and can only fail if out of memory. Conversions from other character types are
transcodings and can fail if source string contains invalid UTF sequences. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<std::ranges::contiguous_range CharRange>
requires(CharTypeConvertibleWithNSString<std::ranges::range_value_t<CharRange>>)
inline auto makeCFString(const CharRange & range) -> CFStringRef __nullable {
Expand All @@ -430,13 +489,33 @@ inline auto makeCFString(const CharRange & range) -> CFStringRef __nullable {
}
}

/**
Converts a null terminated character string to CFString
@returns nullptr on failure
The type of range's characters can be any of: char, char16_t, char32_t, char8_t, wchar_t.
Convertions from char16_t are exact and can only fail if out of memory. Conversions from other character types are
transcodings and can fail if source string contains invalid UTF sequences. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char>
inline auto makeCFString(const Char * __nullable str) -> CFStringRef __nullable {
if (!str)
return nullptr;
return makeCFString(std::basic_string_view<Char>(str));
}

/**
Converts an initializer list of characters to CFString
@returns nullptr on failure
The type of range's characters can be any of: char, char16_t, char32_t, char8_t, wchar_t.
Convertions from char16_t are exact and can only fail if out of memory. Conversions from other character types are
transcodings and can fail if source string contains invalid UTF sequences. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char>
inline auto makeCFString(const std::initializer_list<Char> & str) {
return makeCFString(std::basic_string_view<Char>(str.begin(), str.size()));
Expand All @@ -445,17 +524,47 @@ inline auto makeCFString(const std::initializer_list<Char> & str) {

#ifdef __OBJC__

/**
Converts any contiguous range of characters to NSString
@returns nil on failure
The type of range's characters can be any of: char, char16_t, char32_t, char8_t, wchar_t.
Convertions from char16_t are exact and can only fail if out of memory. Conversions from other character types are
transcodings and can fail if source string contains invalid UTF sequences. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<std::ranges::contiguous_range CharRange>
requires(CharTypeConvertibleWithNSString<std::ranges::range_value_t<CharRange>>)
inline auto makeNSString(const CharRange & range) {
return (__bridge_transfer NSString *)makeCFString(range);
}

/**
Converts a null terminated character string to NSString
@returns nil on failure
The type of range's characters can be any of: char, char16_t, char32_t, char8_t, wchar_t.
Convertions from char16_t are exact and can only fail if out of memory. Conversions from other character types are
transcodings and can fail if source string contains invalid UTF sequences. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char>
inline auto makeNSString(const Char * __nullable str) {
return (__bridge_transfer NSString *)makeCFString(str);
}

/**
Converts an initializer list of characters to NSString
@returns nil on failure
The type of range's characters can be any of: char, char16_t, char32_t, char8_t, wchar_t.
Convertions from char16_t are exact and can only fail if out of memory. Conversions from other character types are
transcodings and can fail if source string contains invalid UTF sequences. Encodings of char and wchar_t are UTF-8 and UTF-32 respectively.
*/
template<CharTypeConvertibleWithNSString Char>
inline auto makeNSString(const std::initializer_list<Char> & str) {
return (__bridge_transfer NSString *)makeCFString(str);
Expand Down
18 changes: 18 additions & 0 deletions test/BoxUtilTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@
CHECK([box(6) compare:box(5)] == NSOrderedDescending);
CHECK([box(6) compare:box(6)] == NSOrderedSame);

auto b = box(27);
CHECK([b compare:b] == NSOrderedSame);

@try {
NSObject<BoxedComparable> * n;
[box(5) compare:n];
FAIL("able to compare with nil");
} @catch (NSException * exc) {
CHECK([exc.name isEqualToString:NSInvalidArgumentException]);
}

@try {
[box(5) compare:box(5l)];
FAIL("able to compare with different class");
} @catch (NSException * exc) {
CHECK([exc.name isEqualToString:NSInvalidArgumentException]);
}

@try {
[[maybe_unused]] auto obj1 = (NSObject *)[[obj.class alloc] init];
FAIL("able to call init");
Expand Down
12 changes: 12 additions & 0 deletions test/NSStringUtilTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
auto * str = @"abc";
CHECK(r::equal(NSStringCharAccess(str), u"abc"sv));
CHECK(r::equal(NSStringCharAccess(str) | v::reverse, u"cba"sv));
CHECK(NSStringCharAccess(str).getString() == str);
}
{
auto * str = @"яж";
Expand All @@ -110,11 +111,15 @@
CHECK(r::equal(NSStringCharAccess(str) | v::reverse, u"57"sv));
}

CHECK(NSStringCharAccess(nullptr).getString() == nullptr);
CHECK(NSStringCharAccess::iterator().getString() == nullptr);

auto access = NSStringCharAccess(@"abcd");
auto it1 = access.begin() + 1;
auto it2 = it1 + 2;
auto substr = [access.getString() substringWithRange:{NSUInteger(it1.index()), NSUInteger(it2.index() - it1.index())}];
CHECK(NSStringEqual()(substr, @"bc"));
CHECK(it1.getString() == it2.getString());
CHECK(it1 < it2);
CHECK(it1 <= it2);
CHECK(it2 > it1);
Expand All @@ -130,6 +135,7 @@
CHECK(it2 == access.begin());
CHECK(it1 - it2 == 1);
CHECK(1 + it1 == it1 + 1);
CHECK(it2[1] == *it1);
it1 -= 1;
CHECK(it1 == it2);
it1 += 1;
Expand All @@ -149,6 +155,7 @@
CHECK(NSStringCharAccess(@"").empty());

CHECK(CFStringCompare(NSStringCharAccess(@"abcd").getCFString(), CFSTR("abcd"), 0) == 0);
CHECK(NSStringCharAccess(nullptr).getCFString() == nullptr);
}

TEST_CASE("makeNSString") {
Expand Down Expand Up @@ -282,6 +289,7 @@
auto malformed = makeNSString(u"\xD800");

{
CHECK(makeStdString<char16_t>((NSString *)nullptr) == u"");
CHECK(makeStdString<char16_t>(@"abc") == u"abc");
CHECK(makeStdString<char16_t>(@"abc", 1) == u"bc");
CHECK(makeStdString<char16_t>(@"abc", 1, 1) == u"b");
Expand All @@ -290,6 +298,7 @@
CHECK(makeStdString<char16_t>(malformed) == u"\xD800");
}
{
CHECK(makeStdString<char>((NSString *)nullptr) == "");
CHECK(makeStdString<char>(@"abc") == "abc");
CHECK(makeStdString<char>(@"abc", 1) == "bc");
CHECK(makeStdString<char>(@"abc", 1, 1) == "b");
Expand All @@ -298,6 +307,7 @@
CHECK(makeStdString<char>(malformed) == "");
}
{
CHECK(makeStdString<char8_t>((NSString *)nullptr) == u8"");
CHECK(makeStdString<char8_t>(@"abc") == u8"abc");
CHECK(makeStdString<char8_t>(@"abc", 1) == u8"bc");
CHECK(makeStdString<char8_t>(@"abc", 1, 1) == u8"b");
Expand All @@ -306,6 +316,7 @@
CHECK(makeStdString<char8_t>(malformed) == u8"");
}
{
CHECK(makeStdString<char32_t>((NSString *)nullptr) == U"");
CHECK(makeStdString<char32_t>(@"abc") == U"abc");
CHECK(makeStdString<char32_t>(@"abc", 1) == U"bc");
CHECK(makeStdString<char32_t>(@"abc", 1, 1) == U"b");
Expand All @@ -314,6 +325,7 @@
CHECK(makeStdString<char32_t>(malformed) == U"");
}
{
CHECK(makeStdString<wchar_t>((NSString *)nullptr) == L"");
CHECK(makeStdString<wchar_t>(@"abc") == L"abc");
CHECK(makeStdString<wchar_t>(@"abc", 1) == L"bc");
CHECK(makeStdString<wchar_t>(@"abc", 1, 1) == L"b");
Expand Down
Loading

0 comments on commit 4a95cd5

Please sign in to comment.