Home | History | Annotate | Download | only in src
      1 /*
      2 **
      3 ** Copyright 2010, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 #include <assert.h>
     19 #include <string.h>
     20 
     21 #define LOG_TAG "LatinIME: unigram_dictionary.cpp"
     22 
     23 #include "char_utils.h"
     24 #include "dictionary.h"
     25 #include "unigram_dictionary.h"
     26 
     27 #include "binary_format.h"
     28 
     29 namespace latinime {
     30 
     31 const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] =
     32         { { 'a', 'e' },
     33         { 'o', 'e' },
     34         { 'u', 'e' } };
     35 
     36 // TODO: check the header
     37 UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultiplier,
     38         int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars,
     39         const bool isLatestDictVersion)
     40     : DICT_ROOT(streamStart + NEW_DICTIONARY_HEADER_SIZE),
     41     MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords),
     42     MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion),
     43     TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier),
     44       // TODO : remove this variable.
     45     ROOT_POS(0),
     46     BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(int)),
     47     MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
     48     if (DEBUG_DICT) {
     49         LOGI("UnigramDictionary - constructor");
     50     }
     51     mCorrection = new Correction(typedLetterMultiplier, fullWordMultiplier);
     52 }
     53 
     54 UnigramDictionary::~UnigramDictionary() {
     55     delete mCorrection;
     56 }
     57 
     58 static inline unsigned int getCodesBufferSize(const int* codes, const int codesSize,
     59         const int MAX_PROXIMITY_CHARS) {
     60     return sizeof(*codes) * MAX_PROXIMITY_CHARS * codesSize;
     61 }
     62 
     63 bool UnigramDictionary::isDigraph(const int* codes, const int i, const int codesSize) const {
     64 
     65     // There can't be a digraph if we don't have at least 2 characters to examine
     66     if (i + 2 > codesSize) return false;
     67 
     68     // Search for the first char of some digraph
     69     int lastDigraphIndex = -1;
     70     const int thisChar = codes[i * MAX_PROXIMITY_CHARS];
     71     for (lastDigraphIndex = sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0]) - 1;
     72             lastDigraphIndex >= 0; --lastDigraphIndex) {
     73         if (thisChar == GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].first) break;
     74     }
     75     // No match: return early
     76     if (lastDigraphIndex < 0) return false;
     77 
     78     // It's an interesting digraph if the second char matches too.
     79     return GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].second == codes[(i + 1) * MAX_PROXIMITY_CHARS];
     80 }
     81 
     82 // Mostly the same arguments as the non-recursive version, except:
     83 // codes is the original value. It points to the start of the work buffer, and gets passed as is.
     84 // codesSize is the size of the user input (thus, it is the size of codesSrc).
     85 // codesDest is the current point in the work buffer.
     86 // codesSrc is the current point in the user-input, original, content-unmodified buffer.
     87 // codesRemain is the remaining size in codesSrc.
     88 void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
     89         const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
     90         const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain,
     91         const int currentDepth, int* codesDest, unsigned short* outWords, int* frequencies) {
     92 
     93     if (currentDepth < MAX_UMLAUT_SEARCH_DEPTH) {
     94         for (int i = 0; i < codesRemain; ++i) {
     95             if (isDigraph(codesSrc, i, codesRemain)) {
     96                 // Found a digraph. We will try both spellings. eg. the word is "pruefen"
     97 
     98                 // Copy the word up to the first char of the digraph, then continue processing
     99                 // on the remaining part of the word, skipping the second char of the digraph.
    100                 // In our example, copy "pru" and continue running on "fen"
    101                 // Make i the index of the second char of the digraph for simplicity. Forgetting
    102                 // to do that results in an infinite recursion so take care!
    103                 ++i;
    104                 memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
    105                 getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
    106                         codesBuffer, codesBufferSize, flags,
    107                         codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
    108                         currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, outWords,
    109                         frequencies);
    110 
    111                 // Copy the second char of the digraph in place, then continue processing on
    112                 // the remaining part of the word.
    113                 // In our example, after "pru" in the buffer copy the "e", and continue on "fen"
    114                 memcpy(codesDest + i * MAX_PROXIMITY_CHARS, codesSrc + i * MAX_PROXIMITY_CHARS,
    115                         BYTES_IN_ONE_CHAR);
    116                 getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
    117                         codesBuffer, codesBufferSize, flags, codesSrc + i * MAX_PROXIMITY_CHARS,
    118                         codesRemain - i, currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS,
    119                         outWords, frequencies);
    120                 return;
    121             }
    122         }
    123     }
    124 
    125     // If we come here, we hit the end of the word: let's check it against the dictionary.
    126     // In our example, we'll come here once for "prufen" and then once for "pruefen".
    127     // If the word contains several digraphs, we'll come it for the product of them.
    128     // eg. if the word is "ueberpruefen" we'll test, in order, against
    129     // "uberprufen", "uberpruefen", "ueberprufen", "ueberpruefen".
    130     const unsigned int remainingBytes = BYTES_IN_ONE_CHAR * codesRemain;
    131     if (0 != remainingBytes)
    132         memcpy(codesDest, codesSrc, remainingBytes);
    133 
    134     getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
    135             (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies,
    136             flags);
    137 }
    138 
    139 int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
    140         const int *ycoordinates, const int *codes, const int codesSize, const int flags,
    141         unsigned short *outWords, int *frequencies) {
    142 
    143     if (REQUIRES_GERMAN_UMLAUT_PROCESSING & flags)
    144     { // Incrementally tune the word and try all possibilities
    145         int codesBuffer[getCodesBufferSize(codes, codesSize, MAX_PROXIMITY_CHARS)];
    146         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
    147                 codesSize, flags, codes, codesSize, 0, codesBuffer, outWords, frequencies);
    148     } else { // Normal processing
    149         getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize,
    150                 outWords, frequencies, flags);
    151     }
    152 
    153     PROF_START(20);
    154     // Get the word count
    155     int suggestedWordsCount = 0;
    156     while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) {
    157         suggestedWordsCount++;
    158     }
    159 
    160     if (DEBUG_DICT) {
    161         LOGI("Returning %d words", suggestedWordsCount);
    162         /// Print the returned words
    163         for (int j = 0; j < suggestedWordsCount; ++j) {
    164 #ifdef FLAG_DBG
    165             short unsigned int* w = mOutputChars + j * MAX_WORD_LENGTH;
    166             char s[MAX_WORD_LENGTH];
    167             for (int i = 0; i <= MAX_WORD_LENGTH; i++) s[i] = w[i];
    168             LOGI("%s %i", s, mFrequencies[j]);
    169 #endif
    170         }
    171     }
    172     PROF_END(20);
    173     PROF_CLOSE;
    174     return suggestedWordsCount;
    175 }
    176 
    177 void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
    178         const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize,
    179         unsigned short *outWords, int *frequencies, const int flags) {
    180 
    181     PROF_OPEN;
    182     PROF_START(0);
    183     initSuggestions(
    184             proximityInfo, xcoordinates, ycoordinates, codes, codesSize, outWords, frequencies);
    185     if (DEBUG_DICT) assert(codesSize == mInputLength);
    186 
    187     const int maxDepth = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
    188     mCorrection->initCorrection(mProximityInfo, mInputLength, maxDepth);
    189     PROF_END(0);
    190 
    191     const bool useFullEditDistance = USE_FULL_EDIT_DISTANCE & flags;
    192     // TODO: remove
    193     PROF_START(1);
    194     getSuggestionCandidates(useFullEditDistance);
    195     PROF_END(1);
    196 
    197     PROF_START(2);
    198     // Note: This line is intentionally left blank
    199     PROF_END(2);
    200 
    201     PROF_START(3);
    202     // Note: This line is intentionally left blank
    203     PROF_END(3);
    204 
    205     PROF_START(4);
    206     // Note: This line is intentionally left blank
    207     PROF_END(4);
    208 
    209     PROF_START(5);
    210     // Suggestions with missing space
    211     if (SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER
    212             && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION) {
    213         for (int i = 1; i < codesSize; ++i) {
    214             if (DEBUG_DICT) {
    215                 LOGI("--- Suggest missing space characters %d", i);
    216             }
    217             getMissingSpaceWords(mInputLength, i, mCorrection, useFullEditDistance);
    218         }
    219     }
    220     PROF_END(5);
    221 
    222     PROF_START(6);
    223     if (SUGGEST_WORDS_WITH_SPACE_PROXIMITY && proximityInfo) {
    224         // The first and last "mistyped spaces" are taken care of by excessive character handling
    225         for (int i = 1; i < codesSize - 1; ++i) {
    226             if (DEBUG_DICT) {
    227                 LOGI("--- Suggest words with proximity space %d", i);
    228             }
    229             const int x = xcoordinates[i];
    230             const int y = ycoordinates[i];
    231             if (DEBUG_PROXIMITY_INFO) {
    232                 LOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
    233                         i, x, y, proximityInfo->hasSpaceProximity(x, y));
    234             }
    235             if (proximityInfo->hasSpaceProximity(x, y)) {
    236                 getMistypedSpaceWords(mInputLength, i, mCorrection, useFullEditDistance);
    237             }
    238         }
    239     }
    240     PROF_END(6);
    241 }
    242 
    243 void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
    244         const int *yCoordinates, const int *codes, const int codesSize,
    245         unsigned short *outWords, int *frequencies) {
    246     if (DEBUG_DICT) {
    247         LOGI("initSuggest");
    248     }
    249     mFrequencies = frequencies;
    250     mOutputChars = outWords;
    251     mInputLength = codesSize;
    252     proximityInfo->setInputParams(codes, codesSize, xCoordinates, yCoordinates);
    253     mProximityInfo = proximityInfo;
    254 }
    255 
    256 static inline void registerNextLetter(unsigned short c, int *nextLetters, int nextLettersSize) {
    257     if (c < nextLettersSize) {
    258         nextLetters[c]++;
    259     }
    260 }
    261 
    262 // TODO: We need to optimize addWord by using STL or something
    263 // TODO: This needs to take an const unsigned short* and not tinker with its contents
    264 bool UnigramDictionary::addWord(unsigned short *word, int length, int frequency) {
    265     word[length] = 0;
    266     if (DEBUG_DICT && DEBUG_SHOW_FOUND_WORD) {
    267 #ifdef FLAG_DBG
    268         char s[length + 1];
    269         for (int i = 0; i <= length; i++) s[i] = word[i];
    270         LOGI("Found word = %s, freq = %d", s, frequency);
    271 #endif
    272     }
    273     if (length > MAX_WORD_LENGTH) {
    274         if (DEBUG_DICT) {
    275             LOGI("Exceeded max word length.");
    276         }
    277         return false;
    278     }
    279 
    280     // Find the right insertion point
    281     int insertAt = 0;
    282     while (insertAt < MAX_WORDS) {
    283         // TODO: How should we sort words with the same frequency?
    284         if (frequency > mFrequencies[insertAt]) {
    285             break;
    286         }
    287         insertAt++;
    288     }
    289     if (insertAt < MAX_WORDS) {
    290         if (DEBUG_DICT) {
    291 #ifdef FLAG_DBG
    292             char s[length + 1];
    293             for (int i = 0; i <= length; i++) s[i] = word[i];
    294             LOGI("Added word = %s, freq = %d, %d", s, frequency, S_INT_MAX);
    295 #endif
    296         }
    297         memmove((char*) mFrequencies + (insertAt + 1) * sizeof(mFrequencies[0]),
    298                (char*) mFrequencies + insertAt * sizeof(mFrequencies[0]),
    299                (MAX_WORDS - insertAt - 1) * sizeof(mFrequencies[0]));
    300         mFrequencies[insertAt] = frequency;
    301         memmove((char*) mOutputChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
    302                (char*) mOutputChars + insertAt * MAX_WORD_LENGTH * sizeof(short),
    303                (MAX_WORDS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
    304         unsigned short *dest = mOutputChars + insertAt * MAX_WORD_LENGTH;
    305         while (length--) {
    306             *dest++ = *word++;
    307         }
    308         *dest = 0; // NULL terminate
    309         if (DEBUG_DICT) {
    310             LOGI("Added word at %d", insertAt);
    311         }
    312         return true;
    313     }
    314     return false;
    315 }
    316 
    317 static const char QUOTE = '\'';
    318 static const char SPACE = ' ';
    319 
    320 void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance) {
    321     // TODO: Remove setCorrectionParams
    322     mCorrection->setCorrectionParams(0, 0, 0,
    323             -1 /* spaceProximityPos */, -1 /* missingSpacePos */, useFullEditDistance);
    324     int rootPosition = ROOT_POS;
    325     // Get the number of children of root, then increment the position
    326     int childCount = Dictionary::getCount(DICT_ROOT, &rootPosition);
    327     int outputIndex = 0;
    328 
    329     mCorrection->initCorrectionState(rootPosition, childCount, (mInputLength <= 0));
    330 
    331     // Depth first search
    332     while (outputIndex >= 0) {
    333         if (mCorrection->initProcessState(outputIndex)) {
    334             int siblingPos = mCorrection->getTreeSiblingPos(outputIndex);
    335             int firstChildPos;
    336 
    337             const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
    338                     mCorrection, &childCount, &firstChildPos, &siblingPos);
    339             // Update next sibling pos
    340             mCorrection->setTreeSiblingPos(outputIndex, siblingPos);
    341 
    342             if (needsToTraverseChildrenNodes) {
    343                 // Goes to child node
    344                 outputIndex = mCorrection->goDownTree(outputIndex, childCount, firstChildPos);
    345             }
    346         } else {
    347             // Goes to parent sibling node
    348             outputIndex = mCorrection->getTreeParentIndex(outputIndex);
    349         }
    350     }
    351 }
    352 
    353 void UnigramDictionary::getMissingSpaceWords(
    354         const int inputLength, const int missingSpacePos, Correction *correction,
    355         const bool useFullEditDistance) {
    356     correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
    357             -1 /* transposedPos */, -1 /* spaceProximityPos */, missingSpacePos,
    358             useFullEditDistance);
    359     getSplitTwoWordsSuggestion(inputLength, correction);
    360 }
    361 
    362 void UnigramDictionary::getMistypedSpaceWords(
    363         const int inputLength, const int spaceProximityPos, Correction *correction,
    364         const bool useFullEditDistance) {
    365     correction->setCorrectionParams(-1 /* skipPos */, -1 /* excessivePos */,
    366             -1 /* transposedPos */, spaceProximityPos, -1 /* missingSpacePos */,
    367             useFullEditDistance);
    368     getSplitTwoWordsSuggestion(inputLength, correction);
    369 }
    370 
    371 inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c,
    372         const int inputIndex, const int skipPos, const int depth) {
    373     const unsigned short userTypedChar = mProximityInfo->getPrimaryCharAt(inputIndex);
    374     // Skip the ' or other letter and continue deeper
    375     return (c == QUOTE && userTypedChar != QUOTE) || skipPos == depth;
    376 }
    377 
    378 inline void UnigramDictionary::onTerminal(const int freq, Correction *correction) {
    379     int wordLength;
    380     unsigned short* wordPointer;
    381     const int finalFreq = correction->getFinalFreq(freq, &wordPointer, &wordLength);
    382     if (finalFreq >= 0) {
    383         addWord(wordPointer, wordLength, finalFreq);
    384     }
    385 }
    386 
    387 void UnigramDictionary::getSplitTwoWordsSuggestion(
    388         const int inputLength, Correction* correction) {
    389     const int spaceProximityPos = correction->getSpaceProximityPos();
    390     const int missingSpacePos = correction->getMissingSpacePos();
    391     if (DEBUG_DICT) {
    392         int inputCount = 0;
    393         if (spaceProximityPos >= 0) ++inputCount;
    394         if (missingSpacePos >= 0) ++inputCount;
    395         assert(inputCount <= 1);
    396     }
    397     const bool isSpaceProximity = spaceProximityPos >= 0;
    398     const int firstWordStartPos = 0;
    399     const int secondWordStartPos = isSpaceProximity ? (spaceProximityPos + 1) : missingSpacePos;
    400     const int firstWordLength = isSpaceProximity ? spaceProximityPos : missingSpacePos;
    401     const int secondWordLength = isSpaceProximity
    402             ? (inputLength - spaceProximityPos - 1)
    403             : (inputLength - missingSpacePos);
    404 
    405     if (inputLength >= MAX_WORD_LENGTH) return;
    406     if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos
    407             || firstWordStartPos < 0 || secondWordStartPos + secondWordLength > inputLength)
    408         return;
    409 
    410     const int newWordLength = firstWordLength + secondWordLength + 1;
    411     // Allocating variable length array on stack
    412     unsigned short word[newWordLength];
    413     const int firstFreq = getMostFrequentWordLike(firstWordStartPos, firstWordLength, mWord);
    414     if (DEBUG_DICT) {
    415         LOGI("First freq: %d", firstFreq);
    416     }
    417     if (firstFreq <= 0) return;
    418 
    419     for (int i = 0; i < firstWordLength; ++i) {
    420         word[i] = mWord[i];
    421     }
    422 
    423     const int secondFreq = getMostFrequentWordLike(secondWordStartPos, secondWordLength, mWord);
    424     if (DEBUG_DICT) {
    425         LOGI("Second  freq:  %d", secondFreq);
    426     }
    427     if (secondFreq <= 0) return;
    428 
    429     word[firstWordLength] = SPACE;
    430     for (int i = (firstWordLength + 1); i < newWordLength; ++i) {
    431         word[i] = mWord[i - firstWordLength - 1];
    432     }
    433 
    434     const int pairFreq = mCorrection->getFreqForSplitTwoWords(firstFreq, secondFreq, word);
    435     if (DEBUG_DICT) {
    436         LOGI("Split two words:  %d, %d, %d, %d", firstFreq, secondFreq, pairFreq, inputLength);
    437     }
    438     addWord(word, newWordLength, pairFreq);
    439     return;
    440 }
    441 
    442 // Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
    443 // interface.
    444 inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex,
    445         const int inputLength, unsigned short *word) {
    446     uint16_t inWord[inputLength];
    447 
    448     for (int i = 0; i < inputLength; ++i) {
    449         inWord[i] = (uint16_t)mProximityInfo->getPrimaryCharAt(startInputIndex + i);
    450     }
    451     return getMostFrequentWordLikeInner(inWord, inputLength, word);
    452 }
    453 
    454 // This function will take the position of a character array within a CharGroup,
    455 // and check it actually like-matches the word in inWord starting at startInputIndex,
    456 // that is, it matches it with case and accents squashed.
    457 // The function returns true if there was a full match, false otherwise.
    458 // The function will copy on-the-fly the characters in the CharGroup to outNewWord.
    459 // It will also place the end position of the array in outPos; in outInputIndex,
    460 // it will place the index of the first char AFTER the match if there was a match,
    461 // and the initial position if there was not. It makes sense because if there was
    462 // a match we want to continue searching, but if there was not, we want to go to
    463 // the next CharGroup.
    464 // In and out parameters may point to the same location. This function takes care
    465 // not to use any input parameters after it wrote into its outputs.
    466 static inline bool testCharGroupForContinuedLikeness(const uint8_t flags,
    467         const uint8_t* const root, const int startPos,
    468         const uint16_t* const inWord, const int startInputIndex,
    469         int32_t* outNewWord, int* outInputIndex, int* outPos) {
    470     const bool hasMultipleChars = (0 != (UnigramDictionary::FLAG_HAS_MULTIPLE_CHARS & flags));
    471     int pos = startPos;
    472     int32_t character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
    473     int32_t baseChar = Dictionary::toBaseLowerCase(character);
    474     const uint16_t wChar = Dictionary::toBaseLowerCase(inWord[startInputIndex]);
    475 
    476     if (baseChar != wChar) {
    477         *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos;
    478         *outInputIndex = startInputIndex;
    479         return false;
    480     }
    481     int inputIndex = startInputIndex;
    482     outNewWord[inputIndex] = character;
    483     if (hasMultipleChars) {
    484         character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
    485         while (NOT_A_CHARACTER != character) {
    486             baseChar = Dictionary::toBaseLowerCase(character);
    487             if (Dictionary::toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
    488                 *outPos = BinaryFormat::skipOtherCharacters(root, pos);
    489                 *outInputIndex = startInputIndex;
    490                 return false;
    491             }
    492             outNewWord[inputIndex] = character;
    493             character = BinaryFormat::getCharCodeAndForwardPointer(root, &pos);
    494         }
    495     }
    496     *outInputIndex = inputIndex + 1;
    497     *outPos = pos;
    498     return true;
    499 }
    500 
    501 // This function is invoked when a word like the word searched for is found.
    502 // It will compare the frequency to the max frequency, and if greater, will
    503 // copy the word into the output buffer. In output value maxFreq, it will
    504 // write the new maximum frequency if it changed.
    505 static inline void onTerminalWordLike(const int freq, int32_t* newWord, const int length,
    506         short unsigned int* outWord, int* maxFreq) {
    507     if (freq > *maxFreq) {
    508         for (int q = 0; q < length; ++q)
    509             outWord[q] = newWord[q];
    510         outWord[length] = 0;
    511         *maxFreq = freq;
    512     }
    513 }
    514 
    515 // Will find the highest frequency of the words like the one passed as an argument,
    516 // that is, everything that only differs by case/accents.
    517 int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t * const inWord,
    518         const int length, short unsigned int* outWord) {
    519     int32_t newWord[MAX_WORD_LENGTH_INTERNAL];
    520     int depth = 0;
    521     int maxFreq = -1;
    522     const uint8_t* const root = DICT_ROOT;
    523 
    524     mStackChildCount[0] = root[0];
    525     mStackInputIndex[0] = 0;
    526     mStackSiblingPos[0] = 1;
    527     while (depth >= 0) {
    528         const int charGroupCount = mStackChildCount[depth];
    529         int pos = mStackSiblingPos[depth];
    530         for (int charGroupIndex = charGroupCount - 1; charGroupIndex >= 0; --charGroupIndex) {
    531             int inputIndex = mStackInputIndex[depth];
    532             const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
    533             // Test whether all chars in this group match with the word we are searching for. If so,
    534             // we want to traverse its children (or if the length match, evaluate its frequency).
    535             // Note that this function will output the position regardless, but will only write
    536             // into inputIndex if there is a match.
    537             const bool isAlike = testCharGroupForContinuedLikeness(flags, root, pos, inWord,
    538                     inputIndex, newWord, &inputIndex, &pos);
    539             if (isAlike && (FLAG_IS_TERMINAL & flags) && (inputIndex == length)) {
    540                 const int frequency = BinaryFormat::readFrequencyWithoutMovingPointer(root, pos);
    541                 onTerminalWordLike(frequency, newWord, inputIndex, outWord, &maxFreq);
    542             }
    543             pos = BinaryFormat::skipFrequency(flags, pos);
    544             const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos);
    545             const int childrenNodePos = BinaryFormat::readChildrenPosition(root, flags, pos);
    546             // If we had a match and the word has children, we want to traverse them. We don't have
    547             // to traverse words longer than the one we are searching for, since they will not match
    548             // anyway, so don't traverse unless inputIndex < length.
    549             if (isAlike && (-1 != childrenNodePos) && (inputIndex < length)) {
    550                 // Save position for this depth, to get back to this once children are done
    551                 mStackChildCount[depth] = charGroupIndex;
    552                 mStackSiblingPos[depth] = siblingPos;
    553                 // Prepare stack values for next depth
    554                 ++depth;
    555                 int childrenPos = childrenNodePos;
    556                 mStackChildCount[depth] =
    557                         BinaryFormat::getGroupCountAndForwardPointer(root, &childrenPos);
    558                 mStackSiblingPos[depth] = childrenPos;
    559                 mStackInputIndex[depth] = inputIndex;
    560                 pos = childrenPos;
    561                 // Go to the next depth level.
    562                 ++depth;
    563                 break;
    564             } else {
    565                 // No match, or no children, or word too long to ever match: go the next sibling.
    566                 pos = siblingPos;
    567             }
    568         }
    569         --depth;
    570     }
    571     return maxFreq;
    572 }
    573 
    574 bool UnigramDictionary::isValidWord(const uint16_t* const inWord, const int length) const {
    575     return NOT_VALID_WORD != BinaryFormat::getTerminalPosition(DICT_ROOT, inWord, length);
    576 }
    577 
    578 // TODO: remove this function.
    579 int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offset,
    580         int length) const {
    581     return -1;
    582 }
    583 
    584 // ProcessCurrentNode returns a boolean telling whether to traverse children nodes or not.
    585 // If the return value is false, then the caller should read in the output "nextSiblingPosition"
    586 // to find out the address of the next sibling node and pass it to a new call of processCurrentNode.
    587 // It is worthy to note that when false is returned, the output values other than
    588 // nextSiblingPosition are undefined.
    589 // If the return value is true, then the caller must proceed to traverse the children of this
    590 // node. processCurrentNode will output the information about the children: their count in
    591 // newCount, their position in newChildrenPosition, the traverseAllNodes flag in
    592 // newTraverseAllNodes, the match weight into newMatchRate, the input index into newInputIndex, the
    593 // diffs into newDiffs, the sibling position in nextSiblingPosition, and the output index into
    594 // newOutputIndex. Please also note the following caveat: processCurrentNode does not know when
    595 // there aren't any more nodes at this level, it merely returns the address of the first byte after
    596 // the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any
    597 // given level, as output into newCount when traversing this level's parent.
    598 inline bool UnigramDictionary::processCurrentNode(const int initialPos,
    599         Correction *correction, int *newCount,
    600         int *newChildrenPosition, int *nextSiblingPosition) {
    601     if (DEBUG_DICT) {
    602         correction->checkState();
    603     }
    604     int pos = initialPos;
    605 
    606     // Flags contain the following information:
    607     // - Address type (MASK_GROUP_ADDRESS_TYPE) on two bits:
    608     //   - FLAG_GROUP_ADDRESS_TYPE_{ONE,TWO,THREE}_BYTES means there are children and their address
    609     //     is on the specified number of bytes.
    610     //   - FLAG_GROUP_ADDRESS_TYPE_NOADDRESS means there are no children, and therefore no address.
    611     // - FLAG_HAS_MULTIPLE_CHARS: whether this node has multiple char or not.
    612     // - FLAG_IS_TERMINAL: whether this node is a terminal or not (it may still have children)
    613     // - FLAG_HAS_BIGRAMS: whether this node has bigrams or not
    614     const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(DICT_ROOT, &pos);
    615     const bool hasMultipleChars = (0 != (FLAG_HAS_MULTIPLE_CHARS & flags));
    616     const bool isTerminalNode = (0 != (FLAG_IS_TERMINAL & flags));
    617 
    618     bool needsToInvokeOnTerminal = false;
    619 
    620     // This gets only ONE character from the stream. Next there will be:
    621     // if FLAG_HAS_MULTIPLE CHARS: the other characters of the same node
    622     // else if FLAG_IS_TERMINAL: the frequency
    623     // else if MASK_GROUP_ADDRESS_TYPE is not NONE: the children address
    624     // Note that you can't have a node that both is not a terminal and has no children.
    625     int32_t c = BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos);
    626     assert(NOT_A_CHARACTER != c);
    627 
    628     // We are going to loop through each character and make it look like it's a different
    629     // node each time. To do that, we will process characters in this node in order until
    630     // we find the character terminator. This is signalled by getCharCode* returning
    631     // NOT_A_CHARACTER.
    632     // As a special case, if there is only one character in this node, we must not read the
    633     // next bytes so we will simulate the NOT_A_CHARACTER return by testing the flags.
    634     // This way, each loop run will look like a "virtual node".
    635     do {
    636         // We prefetch the next char. If 'c' is the last char of this node, we will have
    637         // NOT_A_CHARACTER in the next char. From this we can decide whether this virtual node
    638         // should behave as a terminal or not and whether we have children.
    639         const int32_t nextc = hasMultipleChars
    640                 ? BinaryFormat::getCharCodeAndForwardPointer(DICT_ROOT, &pos) : NOT_A_CHARACTER;
    641         const bool isLastChar = (NOT_A_CHARACTER == nextc);
    642         // If there are more chars in this nodes, then this virtual node is not a terminal.
    643         // If we are on the last char, this virtual node is a terminal if this node is.
    644         const bool isTerminal = isLastChar && isTerminalNode;
    645 
    646         Correction::CorrectionType stateType = correction->processCharAndCalcState(
    647                 c, isTerminal);
    648         if (stateType == Correction::TRAVERSE_ALL_ON_TERMINAL
    649                 || stateType == Correction::ON_TERMINAL) {
    650             needsToInvokeOnTerminal = true;
    651         } else if (stateType == Correction::UNRELATED) {
    652             // We found that this is an unrelated character, so we should give up traversing
    653             // this node and its children entirely.
    654             // However we may not be on the last virtual node yet so we skip the remaining
    655             // characters in this node, the frequency if it's there, read the next sibling
    656             // position to output it, then return false.
    657             // We don't have to output other values because we return false, as in
    658             // "don't traverse children".
    659             if (!isLastChar) {
    660                 pos = BinaryFormat::skipOtherCharacters(DICT_ROOT, pos);
    661             }
    662             pos = BinaryFormat::skipFrequency(flags, pos);
    663             *nextSiblingPosition =
    664                     BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
    665             return false;
    666         }
    667 
    668         // Prepare for the next character. Promote the prefetched char to current char - the loop
    669         // will take care of prefetching the next. If we finally found our last char, nextc will
    670         // contain NOT_A_CHARACTER.
    671         c = nextc;
    672     } while (NOT_A_CHARACTER != c);
    673 
    674     if (isTerminalNode) {
    675         if (needsToInvokeOnTerminal) {
    676             // The frequency should be here, because we come here only if this is actually
    677             // a terminal node, and we are on its last char.
    678             const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
    679             onTerminal(freq, mCorrection);
    680         }
    681 
    682         // If there are more chars in this node, then this virtual node has children.
    683         // If we are on the last char, this virtual node has children if this node has.
    684         const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
    685 
    686         // This character matched the typed character (enough to traverse the node at least)
    687         // so we just evaluated it. Now we should evaluate this virtual node's children - that
    688         // is, if it has any. If it has no children, we're done here - so we skip the end of
    689         // the node, output the siblings position, and return false "don't traverse children".
    690         // Note that !hasChildren implies isLastChar, so we know we don't have to skip any
    691         // remaining char in this group for there can't be any.
    692         if (!hasChildren) {
    693             pos = BinaryFormat::skipFrequency(flags, pos);
    694             *nextSiblingPosition =
    695                     BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
    696             return false;
    697         }
    698 
    699         // Optimization: Prune out words that are too long compared to how much was typed.
    700         if (correction->needsToPrune()) {
    701             pos = BinaryFormat::skipFrequency(flags, pos);
    702             *nextSiblingPosition =
    703                     BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
    704             if (DEBUG_DICT_FULL) {
    705                 LOGI("Traversing was pruned.");
    706             }
    707             return false;
    708         }
    709     }
    710 
    711     // Now we finished processing this node, and we want to traverse children. If there are no
    712     // children, we can't come here.
    713     assert(BinaryFormat::hasChildrenInFlags(flags));
    714 
    715     // If this node was a terminal it still has the frequency under the pointer (it may have been
    716     // read, but not skipped - see readFrequencyWithoutMovingPointer).
    717     // Next come the children position, then possibly attributes (attributes are bigrams only for
    718     // now, maybe something related to shortcuts in the future).
    719     // Once this is read, we still need to output the number of nodes in the immediate children of
    720     // this node, so we read and output it before returning true, as in "please traverse children".
    721     pos = BinaryFormat::skipFrequency(flags, pos);
    722     int childrenPos = BinaryFormat::readChildrenPosition(DICT_ROOT, flags, pos);
    723     *nextSiblingPosition = BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
    724     *newCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &childrenPos);
    725     *newChildrenPosition = childrenPos;
    726     return true;
    727 }
    728 
    729 } // namespace latinime
    730