Index: source/i18n/collationiterator.cpp |
diff --git a/source/i18n/collationiterator.cpp b/source/i18n/collationiterator.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..86199bcd177216498d9ae965191d40c4d130c16b |
--- /dev/null |
+++ b/source/i18n/collationiterator.cpp |
@@ -0,0 +1,953 @@ |
+/* |
+******************************************************************************* |
+* Copyright (C) 2010-2014, International Business Machines |
+* Corporation and others. All Rights Reserved. |
+******************************************************************************* |
+* collationiterator.cpp |
+* |
+* created on: 2010oct27 |
+* created by: Markus W. Scherer |
+*/ |
+ |
+#include "utypeinfo.h" // for 'typeid' to work |
+ |
+#include "unicode/utypes.h" |
+ |
+#if !UCONFIG_NO_COLLATION |
+ |
+#include "unicode/ucharstrie.h" |
+#include "unicode/ustringtrie.h" |
+#include "charstr.h" |
+#include "cmemory.h" |
+#include "collation.h" |
+#include "collationdata.h" |
+#include "collationfcd.h" |
+#include "collationiterator.h" |
+#include "normalizer2impl.h" |
+#include "uassert.h" |
+#include "uvectr32.h" |
+ |
+U_NAMESPACE_BEGIN |
+ |
+CollationIterator::CEBuffer::~CEBuffer() {} |
+ |
+UBool |
+CollationIterator::CEBuffer::ensureAppendCapacity(int32_t appCap, UErrorCode &errorCode) { |
+ int32_t capacity = buffer.getCapacity(); |
+ if((length + appCap) <= capacity) { return TRUE; } |
+ if(U_FAILURE(errorCode)) { return FALSE; } |
+ do { |
+ if(capacity < 1000) { |
+ capacity *= 4; |
+ } else { |
+ capacity *= 2; |
+ } |
+ } while(capacity < (length + appCap)); |
+ int64_t *p = buffer.resize(capacity, length); |
+ if(p == NULL) { |
+ errorCode = U_MEMORY_ALLOCATION_ERROR; |
+ return FALSE; |
+ } |
+ return TRUE; |
+} |
+ |
+// State of combining marks skipped in discontiguous contraction. |
+// We create a state object on first use and keep it around deactivated between uses. |
+class SkippedState : public UMemory { |
+public: |
+ // Born active but empty. |
+ SkippedState() : pos(0), skipLengthAtMatch(0) {} |
+ void clear() { |
+ oldBuffer.remove(); |
+ pos = 0; |
+ // The newBuffer is reset by setFirstSkipped(). |
+ } |
+ |
+ UBool isEmpty() const { return oldBuffer.isEmpty(); } |
+ |
+ UBool hasNext() const { return pos < oldBuffer.length(); } |
+ |
+ // Requires hasNext(). |
+ UChar32 next() { |
+ UChar32 c = oldBuffer.char32At(pos); |
+ pos += U16_LENGTH(c); |
+ return c; |
+ } |
+ |
+ // Accounts for one more input code point read beyond the end of the marks buffer. |
+ void incBeyond() { |
+ U_ASSERT(!hasNext()); |
+ ++pos; |
+ } |
+ |
+ // Goes backward through the skipped-marks buffer. |
+ // Returns the number of code points read beyond the skipped marks |
+ // that need to be backtracked through normal input. |
+ int32_t backwardNumCodePoints(int32_t n) { |
+ int32_t length = oldBuffer.length(); |
+ int32_t beyond = pos - length; |
+ if(beyond > 0) { |
+ if(beyond >= n) { |
+ // Not back far enough to re-enter the oldBuffer. |
+ pos -= n; |
+ return n; |
+ } else { |
+ // Back out all beyond-oldBuffer code points and re-enter the buffer. |
+ pos = oldBuffer.moveIndex32(length, beyond - n); |
+ return beyond; |
+ } |
+ } else { |
+ // Go backwards from inside the oldBuffer. |
+ pos = oldBuffer.moveIndex32(pos, -n); |
+ return 0; |
+ } |
+ } |
+ |
+ void setFirstSkipped(UChar32 c) { |
+ skipLengthAtMatch = 0; |
+ newBuffer.setTo(c); |
+ } |
+ |
+ void skip(UChar32 c) { |
+ newBuffer.append(c); |
+ } |
+ |
+ void recordMatch() { skipLengthAtMatch = newBuffer.length(); } |
+ |
+ // Replaces the characters we consumed with the newly skipped ones. |
+ void replaceMatch() { |
+ // Note: UnicodeString.replace() pins pos to at most length(). |
+ oldBuffer.replace(0, pos, newBuffer, 0, skipLengthAtMatch); |
+ pos = 0; |
+ } |
+ |
+ void saveTrieState(const UCharsTrie &trie) { trie.saveState(state); } |
+ void resetToTrieState(UCharsTrie &trie) const { trie.resetToState(state); } |
+ |
+private: |
+ // Combining marks skipped in previous discontiguous-contraction matching. |
+ // After that discontiguous contraction was completed, we start reading them from here. |
+ UnicodeString oldBuffer; |
+ // Combining marks newly skipped in current discontiguous-contraction matching. |
+ // These might have been read from the normal text or from the oldBuffer. |
+ UnicodeString newBuffer; |
+ // Reading index in oldBuffer, |
+ // or counter for how many code points have been read beyond oldBuffer (pos-oldBuffer.length()). |
+ int32_t pos; |
+ // newBuffer.length() at the time of the last matching character. |
+ // When a partial match fails, we back out skipped and partial-matching input characters. |
+ int32_t skipLengthAtMatch; |
+ // We save the trie state before we attempt to match a character, |
+ // so that we can skip it and try the next one. |
+ UCharsTrie::State state; |
+}; |
+ |
+CollationIterator::CollationIterator(const CollationIterator &other) |
+ : UObject(other), |
+ trie(other.trie), |
+ data(other.data), |
+ cesIndex(other.cesIndex), |
+ skipped(NULL), |
+ numCpFwd(other.numCpFwd), |
+ isNumeric(other.isNumeric) { |
+ UErrorCode errorCode = U_ZERO_ERROR; |
+ int32_t length = other.ceBuffer.length; |
+ if(length > 0 && ceBuffer.ensureAppendCapacity(length, errorCode)) { |
+ for(int32_t i = 0; i < length; ++i) { |
+ ceBuffer.set(i, other.ceBuffer.get(i)); |
+ } |
+ ceBuffer.length = length; |
+ } else { |
+ cesIndex = 0; |
+ } |
+} |
+ |
+CollationIterator::~CollationIterator() { |
+ delete skipped; |
+} |
+ |
+UBool |
+CollationIterator::operator==(const CollationIterator &other) const { |
+ // Subclasses: Call this method and then add more specific checks. |
+ // Compare the iterator state but not the collation data (trie & data fields): |
+ // Assume that the caller compares the data. |
+ // Ignore skipped since that should be unused between calls to nextCE(). |
+ // (It only stays around to avoid another memory allocation.) |
+ if(!(typeid(*this) == typeid(other) && |
+ ceBuffer.length == other.ceBuffer.length && |
+ cesIndex == other.cesIndex && |
+ numCpFwd == other.numCpFwd && |
+ isNumeric == other.isNumeric)) { |
+ return FALSE; |
+ } |
+ for(int32_t i = 0; i < ceBuffer.length; ++i) { |
+ if(ceBuffer.get(i) != other.ceBuffer.get(i)) { return FALSE; } |
+ } |
+ return TRUE; |
+} |
+ |
+void |
+CollationIterator::reset() { |
+ cesIndex = ceBuffer.length = 0; |
+ if(skipped != NULL) { skipped->clear(); } |
+} |
+ |
+int32_t |
+CollationIterator::fetchCEs(UErrorCode &errorCode) { |
+ while(U_SUCCESS(errorCode) && nextCE(errorCode) != Collation::NO_CE) { |
+ // No need to loop for each expansion CE. |
+ cesIndex = ceBuffer.length; |
+ } |
+ return ceBuffer.length; |
+} |
+ |
+uint32_t |
+CollationIterator::handleNextCE32(UChar32 &c, UErrorCode &errorCode) { |
+ c = nextCodePoint(errorCode); |
+ return (c < 0) ? Collation::FALLBACK_CE32 : data->getCE32(c); |
+} |
+ |
+UChar |
+CollationIterator::handleGetTrailSurrogate() { |
+ return 0; |
+} |
+ |
+UBool |
+CollationIterator::foundNULTerminator() { |
+ return FALSE; |
+} |
+ |
+UBool |
+CollationIterator::forbidSurrogateCodePoints() const { |
+ return FALSE; |
+} |
+ |
+uint32_t |
+CollationIterator::getDataCE32(UChar32 c) const { |
+ return data->getCE32(c); |
+} |
+ |
+uint32_t |
+CollationIterator::getCE32FromBuilderData(uint32_t /*ce32*/, UErrorCode &errorCode) { |
+ if(U_SUCCESS(errorCode)) { errorCode = U_INTERNAL_PROGRAM_ERROR; } |
+ return 0; |
+} |
+ |
+int64_t |
+CollationIterator::nextCEFromCE32(const CollationData *d, UChar32 c, uint32_t ce32, |
+ UErrorCode &errorCode) { |
+ --ceBuffer.length; // Undo ceBuffer.incLength(). |
+ appendCEsFromCE32(d, c, ce32, TRUE, errorCode); |
+ if(U_SUCCESS(errorCode)) { |
+ return ceBuffer.get(cesIndex++); |
+ } else { |
+ return Collation::NO_CE_PRIMARY; |
+ } |
+} |
+ |
+void |
+CollationIterator::appendCEsFromCE32(const CollationData *d, UChar32 c, uint32_t ce32, |
+ UBool forward, UErrorCode &errorCode) { |
+ while(Collation::isSpecialCE32(ce32)) { |
+ switch(Collation::tagFromCE32(ce32)) { |
+ case Collation::FALLBACK_TAG: |
+ case Collation::RESERVED_TAG_3: |
+ if(U_SUCCESS(errorCode)) { errorCode = U_INTERNAL_PROGRAM_ERROR; } |
+ return; |
+ case Collation::LONG_PRIMARY_TAG: |
+ ceBuffer.append(Collation::ceFromLongPrimaryCE32(ce32), errorCode); |
+ return; |
+ case Collation::LONG_SECONDARY_TAG: |
+ ceBuffer.append(Collation::ceFromLongSecondaryCE32(ce32), errorCode); |
+ return; |
+ case Collation::LATIN_EXPANSION_TAG: |
+ if(ceBuffer.ensureAppendCapacity(2, errorCode)) { |
+ ceBuffer.set(ceBuffer.length, Collation::latinCE0FromCE32(ce32)); |
+ ceBuffer.set(ceBuffer.length + 1, Collation::latinCE1FromCE32(ce32)); |
+ ceBuffer.length += 2; |
+ } |
+ return; |
+ case Collation::EXPANSION32_TAG: { |
+ const uint32_t *ce32s = d->ce32s + Collation::indexFromCE32(ce32); |
+ int32_t length = Collation::lengthFromCE32(ce32); |
+ if(ceBuffer.ensureAppendCapacity(length, errorCode)) { |
+ do { |
+ ceBuffer.appendUnsafe(Collation::ceFromCE32(*ce32s++)); |
+ } while(--length > 0); |
+ } |
+ return; |
+ } |
+ case Collation::EXPANSION_TAG: { |
+ const int64_t *ces = d->ces + Collation::indexFromCE32(ce32); |
+ int32_t length = Collation::lengthFromCE32(ce32); |
+ if(ceBuffer.ensureAppendCapacity(length, errorCode)) { |
+ do { |
+ ceBuffer.appendUnsafe(*ces++); |
+ } while(--length > 0); |
+ } |
+ return; |
+ } |
+ case Collation::BUILDER_DATA_TAG: |
+ ce32 = getCE32FromBuilderData(ce32, errorCode); |
+ if(U_FAILURE(errorCode)) { return; } |
+ if(ce32 == Collation::FALLBACK_CE32) { |
+ d = data->base; |
+ ce32 = d->getCE32(c); |
+ } |
+ break; |
+ case Collation::PREFIX_TAG: |
+ if(forward) { backwardNumCodePoints(1, errorCode); } |
+ ce32 = getCE32FromPrefix(d, ce32, errorCode); |
+ if(forward) { forwardNumCodePoints(1, errorCode); } |
+ break; |
+ case Collation::CONTRACTION_TAG: { |
+ const UChar *p = d->contexts + Collation::indexFromCE32(ce32); |
+ uint32_t defaultCE32 = CollationData::readCE32(p); // Default if no suffix match. |
+ if(!forward) { |
+ // Backward contractions are handled by previousCEUnsafe(). |
+ // c has contractions but they were not found. |
+ ce32 = defaultCE32; |
+ break; |
+ } |
+ UChar32 nextCp; |
+ if(skipped == NULL && numCpFwd < 0) { |
+ // Some portion of nextCE32FromContraction() pulled out here as an ASCII fast path, |
+ // avoiding the function call and the nextSkippedCodePoint() overhead. |
+ nextCp = nextCodePoint(errorCode); |
+ if(nextCp < 0) { |
+ // No more text. |
+ ce32 = defaultCE32; |
+ break; |
+ } else if((ce32 & Collation::CONTRACT_NEXT_CCC) != 0 && |
+ !CollationFCD::mayHaveLccc(nextCp)) { |
+ // All contraction suffixes start with characters with lccc!=0 |
+ // but the next code point has lccc==0. |
+ backwardNumCodePoints(1, errorCode); |
+ ce32 = defaultCE32; |
+ break; |
+ } |
+ } else { |
+ nextCp = nextSkippedCodePoint(errorCode); |
+ if(nextCp < 0) { |
+ // No more text. |
+ ce32 = defaultCE32; |
+ break; |
+ } else if((ce32 & Collation::CONTRACT_NEXT_CCC) != 0 && |
+ !CollationFCD::mayHaveLccc(nextCp)) { |
+ // All contraction suffixes start with characters with lccc!=0 |
+ // but the next code point has lccc==0. |
+ backwardNumSkipped(1, errorCode); |
+ ce32 = defaultCE32; |
+ break; |
+ } |
+ } |
+ ce32 = nextCE32FromContraction(d, ce32, p + 2, defaultCE32, nextCp, errorCode); |
+ if(ce32 == Collation::NO_CE32) { |
+ // CEs from a discontiguous contraction plus the skipped combining marks |
+ // have been appended already. |
+ return; |
+ } |
+ break; |
+ } |
+ case Collation::DIGIT_TAG: |
+ if(isNumeric) { |
+ appendNumericCEs(ce32, forward, errorCode); |
+ return; |
+ } else { |
+ // Fetch the non-numeric-collation CE32 and continue. |
+ ce32 = d->ce32s[Collation::indexFromCE32(ce32)]; |
+ break; |
+ } |
+ case Collation::U0000_TAG: |
+ U_ASSERT(c == 0); |
+ if(forward && foundNULTerminator()) { |
+ // Handle NUL-termination. (Not needed in Java.) |
+ ceBuffer.append(Collation::NO_CE, errorCode); |
+ return; |
+ } else { |
+ // Fetch the normal ce32 for U+0000 and continue. |
+ ce32 = d->ce32s[0]; |
+ break; |
+ } |
+ case Collation::HANGUL_TAG: { |
+ const uint32_t *jamoCE32s = d->jamoCE32s; |
+ c -= Hangul::HANGUL_BASE; |
+ UChar32 t = c % Hangul::JAMO_T_COUNT; |
+ c /= Hangul::JAMO_T_COUNT; |
+ UChar32 v = c % Hangul::JAMO_V_COUNT; |
+ c /= Hangul::JAMO_V_COUNT; |
+ if((ce32 & Collation::HANGUL_NO_SPECIAL_JAMO) != 0) { |
+ // None of the Jamo CE32s are isSpecialCE32(). |
+ // Avoid recursive function calls and per-Jamo tests. |
+ if(ceBuffer.ensureAppendCapacity(t == 0 ? 2 : 3, errorCode)) { |
+ ceBuffer.set(ceBuffer.length, Collation::ceFromCE32(jamoCE32s[c])); |
+ ceBuffer.set(ceBuffer.length + 1, Collation::ceFromCE32(jamoCE32s[19 + v])); |
+ ceBuffer.length += 2; |
+ if(t != 0) { |
+ ceBuffer.appendUnsafe(Collation::ceFromCE32(jamoCE32s[39 + t])); |
+ } |
+ } |
+ return; |
+ } else { |
+ // We should not need to compute each Jamo code point. |
+ // In particular, there should be no offset or implicit ce32. |
+ appendCEsFromCE32(d, U_SENTINEL, jamoCE32s[c], forward, errorCode); |
+ appendCEsFromCE32(d, U_SENTINEL, jamoCE32s[19 + v], forward, errorCode); |
+ if(t == 0) { return; } |
+ // offset 39 = 19 + 21 - 1: |
+ // 19 = JAMO_L_COUNT |
+ // 21 = JAMO_T_COUNT |
+ // -1 = omit t==0 |
+ ce32 = jamoCE32s[39 + t]; |
+ c = U_SENTINEL; |
+ break; |
+ } |
+ } |
+ case Collation::LEAD_SURROGATE_TAG: { |
+ U_ASSERT(forward); // Backward iteration should never see lead surrogate code _unit_ data. |
+ U_ASSERT(U16_IS_LEAD(c)); |
+ UChar trail; |
+ if(U16_IS_TRAIL(trail = handleGetTrailSurrogate())) { |
+ c = U16_GET_SUPPLEMENTARY(c, trail); |
+ ce32 &= Collation::LEAD_TYPE_MASK; |
+ if(ce32 == Collation::LEAD_ALL_UNASSIGNED) { |
+ ce32 = Collation::UNASSIGNED_CE32; // unassigned-implicit |
+ } else if(ce32 == Collation::LEAD_ALL_FALLBACK || |
+ (ce32 = d->getCE32FromSupplementary(c)) == Collation::FALLBACK_CE32) { |
+ // fall back to the base data |
+ d = d->base; |
+ ce32 = d->getCE32FromSupplementary(c); |
+ } |
+ } else { |
+ // c is an unpaired surrogate. |
+ ce32 = Collation::UNASSIGNED_CE32; |
+ } |
+ break; |
+ } |
+ case Collation::OFFSET_TAG: |
+ U_ASSERT(c >= 0); |
+ ceBuffer.append(d->getCEFromOffsetCE32(c, ce32), errorCode); |
+ return; |
+ case Collation::IMPLICIT_TAG: |
+ U_ASSERT(c >= 0); |
+ if(U_IS_SURROGATE(c) && forbidSurrogateCodePoints()) { |
+ ce32 = Collation::FFFD_CE32; |
+ break; |
+ } else { |
+ ceBuffer.append(Collation::unassignedCEFromCodePoint(c), errorCode); |
+ return; |
+ } |
+ } |
+ } |
+ ceBuffer.append(Collation::ceFromSimpleCE32(ce32), errorCode); |
+} |
+ |
+uint32_t |
+CollationIterator::getCE32FromPrefix(const CollationData *d, uint32_t ce32, |
+ UErrorCode &errorCode) { |
+ const UChar *p = d->contexts + Collation::indexFromCE32(ce32); |
+ ce32 = CollationData::readCE32(p); // Default if no prefix match. |
+ p += 2; |
+ // Number of code points read before the original code point. |
+ int32_t lookBehind = 0; |
+ UCharsTrie prefixes(p); |
+ for(;;) { |
+ UChar32 c = previousCodePoint(errorCode); |
+ if(c < 0) { break; } |
+ ++lookBehind; |
+ UStringTrieResult match = prefixes.nextForCodePoint(c); |
+ if(USTRINGTRIE_HAS_VALUE(match)) { |
+ ce32 = (uint32_t)prefixes.getValue(); |
+ } |
+ if(!USTRINGTRIE_HAS_NEXT(match)) { break; } |
+ } |
+ forwardNumCodePoints(lookBehind, errorCode); |
+ return ce32; |
+} |
+ |
+UChar32 |
+CollationIterator::nextSkippedCodePoint(UErrorCode &errorCode) { |
+ if(skipped != NULL && skipped->hasNext()) { return skipped->next(); } |
+ if(numCpFwd == 0) { return U_SENTINEL; } |
+ UChar32 c = nextCodePoint(errorCode); |
+ if(skipped != NULL && !skipped->isEmpty() && c >= 0) { skipped->incBeyond(); } |
+ if(numCpFwd > 0 && c >= 0) { --numCpFwd; } |
+ return c; |
+} |
+ |
+void |
+CollationIterator::backwardNumSkipped(int32_t n, UErrorCode &errorCode) { |
+ if(skipped != NULL && !skipped->isEmpty()) { |
+ n = skipped->backwardNumCodePoints(n); |
+ } |
+ backwardNumCodePoints(n, errorCode); |
+ if(numCpFwd >= 0) { numCpFwd += n; } |
+} |
+ |
+uint32_t |
+CollationIterator::nextCE32FromContraction(const CollationData *d, uint32_t contractionCE32, |
+ const UChar *p, uint32_t ce32, UChar32 c, |
+ UErrorCode &errorCode) { |
+ // c: next code point after the original one |
+ |
+ // Number of code points read beyond the original code point. |
+ // Needed for discontiguous contraction matching. |
+ int32_t lookAhead = 1; |
+ // Number of code points read since the last match (initially only c). |
+ int32_t sinceMatch = 1; |
+ // Normally we only need a contiguous match, |
+ // and therefore need not remember the suffixes state from before a mismatch for retrying. |
+ // If we are already processing skipped combining marks, then we do track the state. |
+ UCharsTrie suffixes(p); |
+ if(skipped != NULL && !skipped->isEmpty()) { skipped->saveTrieState(suffixes); } |
+ UStringTrieResult match = suffixes.firstForCodePoint(c); |
+ for(;;) { |
+ UChar32 nextCp; |
+ if(USTRINGTRIE_HAS_VALUE(match)) { |
+ ce32 = (uint32_t)suffixes.getValue(); |
+ if(!USTRINGTRIE_HAS_NEXT(match) || (c = nextSkippedCodePoint(errorCode)) < 0) { |
+ return ce32; |
+ } |
+ if(skipped != NULL && !skipped->isEmpty()) { skipped->saveTrieState(suffixes); } |
+ sinceMatch = 1; |
+ } else if(match == USTRINGTRIE_NO_MATCH || (nextCp = nextSkippedCodePoint(errorCode)) < 0) { |
+ // No match for c, or partial match (USTRINGTRIE_NO_VALUE) and no further text. |
+ // Back up if necessary, and try a discontiguous contraction. |
+ if((contractionCE32 & Collation::CONTRACT_TRAILING_CCC) != 0 && |
+ // Discontiguous contraction matching extends an existing match. |
+ // If there is no match yet, then there is nothing to do. |
+ ((contractionCE32 & Collation::CONTRACT_SINGLE_CP_NO_MATCH) == 0 || |
+ sinceMatch < lookAhead)) { |
+ // The last character of at least one suffix has lccc!=0, |
+ // allowing for discontiguous contractions. |
+ // UCA S2.1.1 only processes non-starters immediately following |
+ // "a match in the table" (sinceMatch=1). |
+ if(sinceMatch > 1) { |
+ // Return to the state after the last match. |
+ // (Return to sinceMatch=0 and re-fetch the first partially-matched character.) |
+ backwardNumSkipped(sinceMatch, errorCode); |
+ c = nextSkippedCodePoint(errorCode); |
+ lookAhead -= sinceMatch - 1; |
+ sinceMatch = 1; |
+ } |
+ if(d->getFCD16(c) > 0xff) { |
+ return nextCE32FromDiscontiguousContraction( |
+ d, suffixes, ce32, lookAhead, c, errorCode); |
+ } |
+ } |
+ break; |
+ } else { |
+ // Continue after partial match (USTRINGTRIE_NO_VALUE) for c. |
+ // It does not have a result value, therefore it is not itself "a match in the table". |
+ // If a partially-matched c has ccc!=0 then |
+ // it might be skipped in discontiguous contraction. |
+ c = nextCp; |
+ ++sinceMatch; |
+ } |
+ ++lookAhead; |
+ match = suffixes.nextForCodePoint(c); |
+ } |
+ backwardNumSkipped(sinceMatch, errorCode); |
+ return ce32; |
+} |
+ |
+uint32_t |
+CollationIterator::nextCE32FromDiscontiguousContraction( |
+ const CollationData *d, UCharsTrie &suffixes, uint32_t ce32, |
+ int32_t lookAhead, UChar32 c, |
+ UErrorCode &errorCode) { |
+ if(U_FAILURE(errorCode)) { return 0; } |
+ |
+ // UCA section 3.3.2 Contractions: |
+ // Contractions that end with non-starter characters |
+ // are known as discontiguous contractions. |
+ // ... discontiguous contractions must be detected in input text |
+ // whenever the final sequence of non-starter characters could be rearranged |
+ // so as to make a contiguous matching sequence that is canonically equivalent. |
+ |
+ // UCA: http://www.unicode.org/reports/tr10/#S2.1 |
+ // S2.1 Find the longest initial substring S at each point that has a match in the table. |
+ // S2.1.1 If there are any non-starters following S, process each non-starter C. |
+ // S2.1.2 If C is not blocked from S, find if S + C has a match in the table. |
+ // Note: A non-starter in a string is called blocked |
+ // if there is another non-starter of the same canonical combining class or zero |
+ // between it and the last character of canonical combining class 0. |
+ // S2.1.3 If there is a match, replace S by S + C, and remove C. |
+ |
+ // First: Is a discontiguous contraction even possible? |
+ uint16_t fcd16 = d->getFCD16(c); |
+ U_ASSERT(fcd16 > 0xff); // The caller checked this already, as a shortcut. |
+ UChar32 nextCp = nextSkippedCodePoint(errorCode); |
+ if(nextCp < 0) { |
+ // No further text. |
+ backwardNumSkipped(1, errorCode); |
+ return ce32; |
+ } |
+ ++lookAhead; |
+ uint8_t prevCC = (uint8_t)fcd16; |
+ fcd16 = d->getFCD16(nextCp); |
+ if(fcd16 <= 0xff) { |
+ // The next code point after c is a starter (S2.1.1 "process each non-starter"). |
+ backwardNumSkipped(2, errorCode); |
+ return ce32; |
+ } |
+ |
+ // We have read and matched (lookAhead-2) code points, |
+ // read non-matching c and peeked ahead at nextCp. |
+ // Return to the state before the mismatch and continue matching with nextCp. |
+ if(skipped == NULL || skipped->isEmpty()) { |
+ if(skipped == NULL) { |
+ skipped = new SkippedState(); |
+ if(skipped == NULL) { |
+ errorCode = U_MEMORY_ALLOCATION_ERROR; |
+ return 0; |
+ } |
+ } |
+ suffixes.reset(); |
+ if(lookAhead > 2) { |
+ // Replay the partial match so far. |
+ backwardNumCodePoints(lookAhead, errorCode); |
+ suffixes.firstForCodePoint(nextCodePoint(errorCode)); |
+ for(int32_t i = 3; i < lookAhead; ++i) { |
+ suffixes.nextForCodePoint(nextCodePoint(errorCode)); |
+ } |
+ // Skip c (which did not match) and nextCp (which we will try now). |
+ forwardNumCodePoints(2, errorCode); |
+ } |
+ skipped->saveTrieState(suffixes); |
+ } else { |
+ // Reset to the trie state before the failed match of c. |
+ skipped->resetToTrieState(suffixes); |
+ } |
+ |
+ skipped->setFirstSkipped(c); |
+ // Number of code points read since the last match (at this point: c and nextCp). |
+ int32_t sinceMatch = 2; |
+ c = nextCp; |
+ for(;;) { |
+ UStringTrieResult match; |
+ // "If C is not blocked from S, find if S + C has a match in the table." (S2.1.2) |
+ if(prevCC < (fcd16 >> 8) && USTRINGTRIE_HAS_VALUE(match = suffixes.nextForCodePoint(c))) { |
+ // "If there is a match, replace S by S + C, and remove C." (S2.1.3) |
+ // Keep prevCC unchanged. |
+ ce32 = (uint32_t)suffixes.getValue(); |
+ sinceMatch = 0; |
+ skipped->recordMatch(); |
+ if(!USTRINGTRIE_HAS_NEXT(match)) { break; } |
+ skipped->saveTrieState(suffixes); |
+ } else { |
+ // No match for "S + C", skip C. |
+ skipped->skip(c); |
+ skipped->resetToTrieState(suffixes); |
+ prevCC = (uint8_t)fcd16; |
+ } |
+ if((c = nextSkippedCodePoint(errorCode)) < 0) { break; } |
+ ++sinceMatch; |
+ fcd16 = d->getFCD16(c); |
+ if(fcd16 <= 0xff) { |
+ // The next code point after c is a starter (S2.1.1 "process each non-starter"). |
+ break; |
+ } |
+ } |
+ backwardNumSkipped(sinceMatch, errorCode); |
+ UBool isTopDiscontiguous = skipped->isEmpty(); |
+ skipped->replaceMatch(); |
+ if(isTopDiscontiguous && !skipped->isEmpty()) { |
+ // We did get a match after skipping one or more combining marks, |
+ // and we are not in a recursive discontiguous contraction. |
+ // Append CEs from the contraction ce32 |
+ // and then from the combining marks that we skipped before the match. |
+ c = U_SENTINEL; |
+ for(;;) { |
+ appendCEsFromCE32(d, c, ce32, TRUE, errorCode); |
+ // Fetch CE32s for skipped combining marks from the normal data, with fallback, |
+ // rather than from the CollationData where we found the contraction. |
+ if(!skipped->hasNext()) { break; } |
+ c = skipped->next(); |
+ ce32 = getDataCE32(c); |
+ if(ce32 == Collation::FALLBACK_CE32) { |
+ d = data->base; |
+ ce32 = d->getCE32(c); |
+ } else { |
+ d = data; |
+ } |
+ // Note: A nested discontiguous-contraction match |
+ // replaces consumed combining marks with newly skipped ones |
+ // and resets the reading position to the beginning. |
+ } |
+ skipped->clear(); |
+ ce32 = Collation::NO_CE32; // Signal to the caller that the result is in the ceBuffer. |
+ } |
+ return ce32; |
+} |
+ |
+void |
+CollationIterator::appendNumericCEs(uint32_t ce32, UBool forward, UErrorCode &errorCode) { |
+ // Collect digits. |
+ CharString digits; |
+ if(forward) { |
+ for(;;) { |
+ char digit = Collation::digitFromCE32(ce32); |
+ digits.append(digit, errorCode); |
+ if(numCpFwd == 0) { break; } |
+ UChar32 c = nextCodePoint(errorCode); |
+ if(c < 0) { break; } |
+ ce32 = data->getCE32(c); |
+ if(ce32 == Collation::FALLBACK_CE32) { |
+ ce32 = data->base->getCE32(c); |
+ } |
+ if(!Collation::hasCE32Tag(ce32, Collation::DIGIT_TAG)) { |
+ backwardNumCodePoints(1, errorCode); |
+ break; |
+ } |
+ if(numCpFwd > 0) { --numCpFwd; } |
+ } |
+ } else { |
+ for(;;) { |
+ char digit = Collation::digitFromCE32(ce32); |
+ digits.append(digit, errorCode); |
+ UChar32 c = previousCodePoint(errorCode); |
+ if(c < 0) { break; } |
+ ce32 = data->getCE32(c); |
+ if(ce32 == Collation::FALLBACK_CE32) { |
+ ce32 = data->base->getCE32(c); |
+ } |
+ if(!Collation::hasCE32Tag(ce32, Collation::DIGIT_TAG)) { |
+ forwardNumCodePoints(1, errorCode); |
+ break; |
+ } |
+ } |
+ // Reverse the digit string. |
+ char *p = digits.data(); |
+ char *q = p + digits.length() - 1; |
+ while(p < q) { |
+ char digit = *p; |
+ *p++ = *q; |
+ *q-- = digit; |
+ } |
+ } |
+ if(U_FAILURE(errorCode)) { return; } |
+ int32_t pos = 0; |
+ do { |
+ // Skip leading zeros. |
+ while(pos < (digits.length() - 1) && digits[pos] == 0) { ++pos; } |
+ // Write a sequence of CEs for at most 254 digits at a time. |
+ int32_t segmentLength = digits.length() - pos; |
+ if(segmentLength > 254) { segmentLength = 254; } |
+ appendNumericSegmentCEs(digits.data() + pos, segmentLength, errorCode); |
+ pos += segmentLength; |
+ } while(U_SUCCESS(errorCode) && pos < digits.length()); |
+} |
+ |
+void |
+CollationIterator::appendNumericSegmentCEs(const char *digits, int32_t length, UErrorCode &errorCode) { |
+ U_ASSERT(1 <= length && length <= 254); |
+ U_ASSERT(length == 1 || digits[0] != 0); |
+ uint32_t numericPrimary = data->numericPrimary; |
+ // Note: We use primary byte values 2..255: digits are not compressible. |
+ if(length <= 7) { |
+ // Very dense encoding for small numbers. |
+ int32_t value = digits[0]; |
+ for(int32_t i = 1; i < length; ++i) { |
+ value = value * 10 + digits[i]; |
+ } |
+ // Primary weight second byte values: |
+ // 74 byte values 2.. 75 for small numbers in two-byte primary weights. |
+ // 40 byte values 76..115 for medium numbers in three-byte primary weights. |
+ // 16 byte values 116..131 for large numbers in four-byte primary weights. |
+ // 124 byte values 132..255 for very large numbers with 4..127 digit pairs. |
+ int32_t firstByte = 2; |
+ int32_t numBytes = 74; |
+ if(value < numBytes) { |
+ // Two-byte primary for 0..73, good for day & month numbers etc. |
+ uint32_t primary = numericPrimary | ((firstByte + value) << 16); |
+ ceBuffer.append(Collation::makeCE(primary), errorCode); |
+ return; |
+ } |
+ value -= numBytes; |
+ firstByte += numBytes; |
+ numBytes = 40; |
+ if(value < numBytes * 254) { |
+ // Three-byte primary for 74..10233=74+40*254-1, good for year numbers and more. |
+ uint32_t primary = numericPrimary | |
+ ((firstByte + value / 254) << 16) | ((2 + value % 254) << 8); |
+ ceBuffer.append(Collation::makeCE(primary), errorCode); |
+ return; |
+ } |
+ value -= numBytes * 254; |
+ firstByte += numBytes; |
+ numBytes = 16; |
+ if(value < numBytes * 254 * 254) { |
+ // Four-byte primary for 10234..1042489=10234+16*254*254-1. |
+ uint32_t primary = numericPrimary | (2 + value % 254); |
+ value /= 254; |
+ primary |= (2 + value % 254) << 8; |
+ value /= 254; |
+ primary |= (firstByte + value % 254) << 16; |
+ ceBuffer.append(Collation::makeCE(primary), errorCode); |
+ return; |
+ } |
+ // original value > 1042489 |
+ } |
+ U_ASSERT(length >= 7); |
+ |
+ // The second primary byte value 132..255 indicates the number of digit pairs (4..127), |
+ // then we generate primary bytes with those pairs. |
+ // Omit trailing 00 pairs. |
+ // Decrement the value for the last pair. |
+ |
+ // Set the exponent. 4 pairs->132, 5 pairs->133, ..., 127 pairs->255. |
+ int32_t numPairs = (length + 1) / 2; |
+ uint32_t primary = numericPrimary | ((132 - 4 + numPairs) << 16); |
+ // Find the length without trailing 00 pairs. |
+ while(digits[length - 1] == 0 && digits[length - 2] == 0) { |
+ length -= 2; |
+ } |
+ // Read the first pair. |
+ uint32_t pair; |
+ int32_t pos; |
+ if(length & 1) { |
+ // Only "half a pair" if we have an odd number of digits. |
+ pair = digits[0]; |
+ pos = 1; |
+ } else { |
+ pair = digits[0] * 10 + digits[1]; |
+ pos = 2; |
+ } |
+ pair = 11 + 2 * pair; |
+ // Add the pairs of digits between pos and length. |
+ int32_t shift = 8; |
+ while(pos < length) { |
+ if(shift == 0) { |
+ // Every three pairs/bytes we need to store a 4-byte-primary CE |
+ // and start with a new CE with the '0' primary lead byte. |
+ primary |= pair; |
+ ceBuffer.append(Collation::makeCE(primary), errorCode); |
+ primary = numericPrimary; |
+ shift = 16; |
+ } else { |
+ primary |= pair << shift; |
+ shift -= 8; |
+ } |
+ pair = 11 + 2 * (digits[pos] * 10 + digits[pos + 1]); |
+ pos += 2; |
+ } |
+ primary |= (pair - 1) << shift; |
+ ceBuffer.append(Collation::makeCE(primary), errorCode); |
+} |
+ |
+int64_t |
+CollationIterator::previousCE(UVector32 &offsets, UErrorCode &errorCode) { |
+ if(ceBuffer.length > 0) { |
+ // Return the previous buffered CE. |
+ return ceBuffer.get(--ceBuffer.length); |
+ } |
+ offsets.removeAllElements(); |
+ int32_t limitOffset = getOffset(); |
+ UChar32 c = previousCodePoint(errorCode); |
+ if(c < 0) { return Collation::NO_CE; } |
+ if(data->isUnsafeBackward(c, isNumeric)) { |
+ return previousCEUnsafe(c, offsets, errorCode); |
+ } |
+ // Simple, safe-backwards iteration: |
+ // Get a CE going backwards, handle prefixes but no contractions. |
+ uint32_t ce32 = data->getCE32(c); |
+ const CollationData *d; |
+ if(ce32 == Collation::FALLBACK_CE32) { |
+ d = data->base; |
+ ce32 = d->getCE32(c); |
+ } else { |
+ d = data; |
+ } |
+ if(Collation::isSimpleOrLongCE32(ce32)) { |
+ return Collation::ceFromCE32(ce32); |
+ } |
+ appendCEsFromCE32(d, c, ce32, FALSE, errorCode); |
+ if(U_SUCCESS(errorCode)) { |
+ if(ceBuffer.length > 1) { |
+ offsets.addElement(getOffset(), errorCode); |
+ // For an expansion, the offset of each non-initial CE is the limit offset, |
+ // consistent with forward iteration. |
+ while(offsets.size() <= ceBuffer.length) { |
+ offsets.addElement(limitOffset, errorCode); |
+ }; |
+ } |
+ return ceBuffer.get(--ceBuffer.length); |
+ } else { |
+ return Collation::NO_CE_PRIMARY; |
+ } |
+} |
+ |
+int64_t |
+CollationIterator::previousCEUnsafe(UChar32 c, UVector32 &offsets, UErrorCode &errorCode) { |
+ // We just move through the input counting safe and unsafe code points |
+ // without collecting the unsafe-backward substring into a buffer and |
+ // switching to it. |
+ // This is to keep the logic simple. Otherwise we would have to handle |
+ // prefix matching going before the backward buffer, switching |
+ // to iteration and back, etc. |
+ // In the most important case of iterating over a normal string, |
+ // reading from the string itself is already maximally fast. |
+ // The only drawback there is that after getting the CEs we always |
+ // skip backward to the safe character rather than switching out |
+ // of a backwardBuffer. |
+ // But this should not be the common case for previousCE(), |
+ // and correctness and maintainability are more important than |
+ // complex optimizations. |
+ // Find the first safe character before c. |
+ int32_t numBackward = 1; |
+ while((c = previousCodePoint(errorCode)) >= 0) { |
+ ++numBackward; |
+ if(!data->isUnsafeBackward(c, isNumeric)) { |
+ break; |
+ } |
+ } |
+ // Set the forward iteration limit. |
+ // Note: This counts code points. |
+ // We cannot enforce a limit in the middle of a surrogate pair or similar. |
+ numCpFwd = numBackward; |
+ // Reset the forward iterator. |
+ cesIndex = 0; |
+ U_ASSERT(ceBuffer.length == 0); |
+ // Go forward and collect the CEs. |
+ int32_t offset = getOffset(); |
+ while(numCpFwd > 0) { |
+ // nextCE() normally reads one code point. |
+ // Contraction matching and digit specials read more and check numCpFwd. |
+ --numCpFwd; |
+ // Append one or more CEs to the ceBuffer. |
+ (void)nextCE(errorCode); |
+ U_ASSERT(U_FAILURE(errorCode) || ceBuffer.get(ceBuffer.length - 1) != Collation::NO_CE); |
+ // No need to loop for getting each expansion CE from nextCE(). |
+ cesIndex = ceBuffer.length; |
+ // However, we need to write an offset for each CE. |
+ // This is for CollationElementIterator::getOffset() to return |
+ // intermediate offsets from the unsafe-backwards segment. |
+ U_ASSERT(offsets.size() < ceBuffer.length); |
+ offsets.addElement(offset, errorCode); |
+ // For an expansion, the offset of each non-initial CE is the limit offset, |
+ // consistent with forward iteration. |
+ offset = getOffset(); |
+ while(offsets.size() < ceBuffer.length) { |
+ offsets.addElement(offset, errorCode); |
+ }; |
+ } |
+ U_ASSERT(offsets.size() == ceBuffer.length); |
+ // End offset corresponding to just after the unsafe-backwards segment. |
+ offsets.addElement(offset, errorCode); |
+ // Reset the forward iteration limit |
+ // and move backward to before the segment for which we fetched CEs. |
+ numCpFwd = -1; |
+ backwardNumCodePoints(numBackward, errorCode); |
+ // Use the collected CEs and return the last one. |
+ cesIndex = 0; // Avoid cesIndex > ceBuffer.length when that gets decremented. |
+ if(U_SUCCESS(errorCode)) { |
+ return ceBuffer.get(--ceBuffer.length); |
+ } else { |
+ return Collation::NO_CE_PRIMARY; |
+ } |
+} |
+ |
+U_NAMESPACE_END |
+ |
+#endif // !UCONFIG_NO_COLLATION |