Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -569,10 +569,18 @@ case "$host_os" in mingw*) AC_SUBST(OFSTDIOSTREAM_WIN32CONSOLE_M, "OFStdIOStream_Win32Console.m") ;; esac + +AC_ARG_ENABLE(unicode-tables, + AS_HELP_STRING([--disable-unicode-tables], [Disable Unicode tables])) +AS_IF([test x"$enable_unicode_tables" != x"no"], [ + AC_DEFINE(OF_HAVE_UNICODE_TABLES, 1, + [Whether to build with Unicode tables]) + AC_SUBST(UNICODE_M, "unicode.m") +]) AC_CHECK_FUNCS(sigaction) AC_CHECK_FUNCS([arc4random random], break) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -47,10 +47,11 @@ RUNTIME_RUNTIME_LIB_A = @RUNTIME_RUNTIME_LIB_A@ RUN_TESTS = @RUN_TESTS@ TESTPLUGIN = @TESTPLUGIN@ TESTS_LIBS = @TESTS_LIBS@ TEST_LAUNCHER = @TEST_LAUNCHER@ +UNICODE_M = @UNICODE_M@ USE_INCLUDES_ATOMIC = @USE_INCLUDES_ATOMIC@ USE_SRCS_FILES = @USE_SRCS_FILES@ USE_SRCS_PLUGINS = @USE_SRCS_PLUGINS@ USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@ USE_SRCS_THREADS = @USE_SRCS_THREADS@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -86,11 +86,11 @@ base64.m \ crc32.m \ of_asprintf.m \ of_strptime.m \ pbkdf2.m \ - unicode.m \ + ${UNICODE_M} \ ${USE_SRCS_FILES} \ ${USE_SRCS_PLUGINS} \ ${USE_SRCS_SOCKETS} \ ${USE_SRCS_THREADS} SRCS_FILES = OFFile.m \ Index: src/OFMutableString.m ================================================================== --- src/OFMutableString.m +++ src/OFMutableString.m @@ -14,10 +14,11 @@ * file. */ #include "config.h" +#include #include #include #include #include @@ -33,10 +34,25 @@ #import "unicode.h" static struct { Class isa; } placeholder; + +#ifndef OF_HAVE_UNICODE_TABLES +/* Wrap these, as they can be macros. */ +static int +toupperWrapper(int c) +{ + return toupper(c); +} + +static int +tolowerWrapper(int c) +{ + return tolower(c); +} +#endif @interface OFMutableString_placeholder: OFMutableString @end @implementation OFMutableString_placeholder @@ -238,10 +254,11 @@ return (id)&placeholder; return [super alloc]; } +#ifdef OF_HAVE_UNICODE_TABLES - (void)OF_convertWithWordStartTable: (const of_unichar_t *const[])startTable wordMiddleTable: (const of_unichar_t *const[])middleTable wordStartTableSize: (size_t)startTableSize wordMiddleTableSize: (size_t)middleTableSize { @@ -280,10 +297,44 @@ } } objc_autoreleasePoolPop(pool); } +#else +- (void)OF_convertWithWordStartFunction: (int (*)(int))startFunction + wordMiddleFunction: (int (*)(int))middleFunction +{ + void *pool = objc_autoreleasePoolPush(); + const of_unichar_t *characters = [self characters]; + size_t length = [self length]; + bool isStart = true; + + for (size_t i = 0; i < length; i++) { + int (*function)(int) = + (isStart ? startFunction : middleFunction);; + of_unichar_t c = characters[i]; + + if (c <= 0x7F) + [self setCharacter: (int)function(c) + atIndex: i]; + + switch (c) { + case ' ': + case '\t': + case '\n': + case '\r': + isStart = true; + break; + default: + isStart = false; + break; + } + } + + objc_autoreleasePoolPop(pool); +} +#endif - (void)setCharacter: (of_unichar_t)character atIndex: (size_t)index { void *pool = objc_autoreleasePoolPush(); @@ -407,10 +458,11 @@ [self setCharacter: tmp atIndex: i]; } } +#ifdef OF_HAVE_UNICODE_TABLES - (void)uppercase { [self OF_convertWithWordStartTable: of_unicode_uppercase_table wordMiddleTable: of_unicode_uppercase_table wordStartTableSize: OF_UNICODE_UPPERCASE_TABLE_SIZE @@ -430,10 +482,29 @@ [self OF_convertWithWordStartTable: of_unicode_titlecase_table wordMiddleTable: of_unicode_lowercase_table wordStartTableSize: OF_UNICODE_TITLECASE_TABLE_SIZE wordMiddleTableSize: OF_UNICODE_LOWERCASE_TABLE_SIZE]; } +#else +- (void)uppercase +{ + [self OF_convertWithWordStartFunction: toupperWrapper + wordMiddleFunction: toupperWrapper]; +} + +- (void)lowercase +{ + [self OF_convertWithWordStartFunction: tolowerWrapper + wordMiddleFunction: tolowerWrapper]; +} + +- (void)capitalize +{ + [self OF_convertWithWordStartFunction: toupperWrapper + wordMiddleFunction: tolowerWrapper]; +} +#endif - (void)insertString: (OFString*)string atIndex: (size_t)index { [self replaceCharactersInRange: of_range(index, 0) Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -1358,25 +1358,31 @@ for (size_t i = 0; i < minimumLength; i++) { of_unichar_t c = characters[i]; of_unichar_t oc = otherCharacters[i]; +#ifdef OF_HAVE_UNICODE_TABLES if (c >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) { of_unichar_t tc = of_unicode_casefolding_table[c >> 8][c & 0xFF]; if (tc) c = tc; } - if (oc >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) { of_unichar_t tc = of_unicode_casefolding_table[oc >> 8][oc & 0xFF]; if (tc) oc = tc; } +#else + if (c <= 0x7F) + c = toupper(c); + if (oc <= 0x7F) + oc = toupper(oc); +#endif if (c > oc) { objc_autoreleasePoolPop(pool); return OF_ORDERED_DESCENDING; } Index: src/OFString_UTF8.m ================================================================== --- src/OFString_UTF8.m +++ src/OFString_UTF8.m @@ -44,15 +44,21 @@ static inline int memcasecmp(const char *first, const char *second, size_t length) { for (size_t i = 0; i < length; i++) { - if (tolower((unsigned char)first[i]) > - tolower((unsigned char)second[i])) + unsigned char f = first[i]; + unsigned char s = second[i]; + + if (f <= 0x7F) + f = toupper(f); + if (s <= 0x7F) + s = toupper(s); + + if (f > s) return OF_ORDERED_DESCENDING; - if (tolower((unsigned char)first[i]) < - tolower((unsigned char)second[i])) + if (f < s) return OF_ORDERED_ASCENDING; } return OF_ORDERED_SAME; } @@ -785,11 +791,13 @@ @throw [OFInvalidArgumentException exception]; otherCString = [otherString UTF8String]; otherCStringLength = [otherString UTF8StringLength]; +#ifdef OF_HAVE_UNICODE_TABLES if (!_s->isUTF8) { +#endif minimumCStringLength = (_s->cStringLength > otherCStringLength ? otherCStringLength : _s->cStringLength); if ((compare = memcasecmp(_s->cString, otherCString, minimumCStringLength)) == 0) { @@ -802,10 +810,11 @@ if (compare > 0) return OF_ORDERED_DESCENDING; else return OF_ORDERED_ASCENDING; +#ifdef OF_HAVE_UNICODE_TABLES } i = j = 0; while (i < _s->cStringLength && j < otherCStringLength) { @@ -842,10 +851,11 @@ return OF_ORDERED_ASCENDING; i += l1; j += l2; } +#endif if (_s->cStringLength - i > otherCStringLength - j) return OF_ORDERED_DESCENDING; else if (_s->cStringLength - i < otherCStringLength - j) return OF_ORDERED_ASCENDING; Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -28,11 +28,12 @@ #undef OF_HAVE_STDNORETURN #undef OF_HAVE_SYMLINK #undef OF_HAVE_SYNC_BUILTINS #undef OF_HAVE_SYS_SOCKET_H #undef OF_HAVE_THREADS +#undef OF_HAVE_UNICODE_TABLES #undef OF_HAVE__THREAD_LOCAL #undef OF_HAVE___THREAD #undef OF_NINTENDO_DS #undef OF_OBJFW_RUNTIME #undef OF_UNIVERSAL #undef SIZE_MAX Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -97,19 +97,27 @@ [@"cd" compare: @"bc"] == OF_ORDERED_DESCENDING && [@"ä" compare: @"ö"] == OF_ORDERED_ASCENDING && [@"€" compare: @"ß"] == OF_ORDERED_DESCENDING && [@"aa" compare: @"z"] == OF_ORDERED_ASCENDING) +#ifdef OF_HAVE_UNICODE_TABLES TEST(@"-[caseInsensitiveCompare:]", [@"a" caseInsensitiveCompare: @"A"] == OF_ORDERED_SAME && [@"Ä" caseInsensitiveCompare: @"ä"] == OF_ORDERED_SAME && [@"я" caseInsensitiveCompare: @"Я"] == OF_ORDERED_SAME && [@"€" caseInsensitiveCompare: @"ß"] == OF_ORDERED_DESCENDING && [@"ß" caseInsensitiveCompare: @"→"] == OF_ORDERED_ASCENDING && [@"AA" caseInsensitiveCompare: @"z"] == OF_ORDERED_ASCENDING && [[OFString stringWithUTF8String: "ABC"] caseInsensitiveCompare: [OFString stringWithUTF8String: "AbD"]] == [@"abc" compare: @"abd"]) +#else + TEST(@"-[caseInsensitiveCompare:]", + [@"a" caseInsensitiveCompare: @"A"] == OF_ORDERED_SAME && + [@"AA" caseInsensitiveCompare: @"z"] == OF_ORDERED_ASCENDING && + [[OFString stringWithUTF8String: "ABC"] caseInsensitiveCompare: + [OFString stringWithUTF8String: "AbD"]] == [@"abc" compare: @"abd"]) +#endif TEST(@"-[hash] is the same if -[isEqual:] is true", [s[0] hash] == [s[2] hash]) TEST(@"-[description]", [[s[0] description] isEqual: s[0]]) @@ -136,10 +144,11 @@ TEST(@"-[reverse]", R([s[0] reverse]) && [s[0] isEqual: @"3𝄞1€sät"]) s[1] = [OFMutableString stringWithString: @"abc"]; +#ifdef OF_HAVE_UNICODE_TABLES TEST(@"-[uppercase]", R([s[0] uppercase]) && [s[0] isEqual: @"3𝄞1€SÄT"] && R([s[1] uppercase]) && [s[1] isEqual: @"ABC"]) TEST(@"-[lowercase]", R([s[0] lowercase]) && @@ -152,10 +161,28 @@ TEST(@"-[lowercaseString]", R([s[0] uppercase]) && [[s[0] lowercaseString] isEqual: @"3𝄞1€sät"]) TEST(@"-[capitalizedString]", [[@"džbla tdžst TDŽST" capitalizedString] isEqual: @"Džbla Tdžst Tdžst"]) +#else + TEST(@"-[uppercase]", R([s[0] uppercase]) && + [s[0] isEqual: @"3𝄞1€SäT"] && + R([s[1] uppercase]) && [s[1] isEqual: @"ABC"]) + + TEST(@"-[lowercase]", R([s[0] lowercase]) && + [s[0] isEqual: @"3𝄞1€sät"] && + R([s[1] lowercase]) && [s[1] isEqual: @"abc"]) + + TEST(@"-[uppercaseString]", + [[s[0] uppercaseString] isEqual: @"3𝄞1€SäT"]) + + TEST(@"-[lowercaseString]", R([s[0] uppercase]) && + [[s[0] lowercaseString] isEqual: @"3𝄞1€sät"]) + + TEST(@"-[capitalizedString]", [[@"džbla tdžst TDŽST" capitalizedString] + isEqual: @"džbla Tdžst TDŽst"]) +#endif TEST(@"+[stringWithUTF8String:length:]", (s[0] = [OFMutableString stringWithUTF8String: "\xEF\xBB\xBF" "foobar" length: 6]) &&