1a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka/* 2a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * Copyright (C) 2013 The Android Open Source Project 3a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * 4a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * Licensed under the Apache License, Version 2.0 (the "License"); 5a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * you may not use this file except in compliance with the License. 6a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * You may obtain a copy of the License at 7a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * 8a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * http://www.apache.org/licenses/LICENSE-2.0 9a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * 10a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * Unless required by applicable law or agreed to in writing, software 11a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * distributed under the License is distributed on an "AS IS" BASIS, 12a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * See the License for the specific language governing permissions and 14a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka * limitations under the License. 15a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka */ 16a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 17a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka#ifndef LATINIME_TYPING_TRAVERSAL_H 18a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka#define LATINIME_TYPING_TRAVERSAL_H 19a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 20a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka#include <stdint.h> 21a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 22a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka#include "defines.h" 23b68e73448104714e8f12f89a1e00fb10b5fd14c4Ken Wakasa#include "suggest/core/dicnode/dic_node.h" 24b68e73448104714e8f12f89a1e00fb10b5fd14c4Ken Wakasa#include "suggest/core/dicnode/dic_node_vector.h" 2529432f843a8cd6ffb2be286104964592e80d77c9Ken Wakasa#include "suggest/core/layout/proximity_info_state.h" 267a06a792871c38517264fcb63b80a9c09bfe4766Keisuke Kuroynagi#include "suggest/core/layout/proximity_info_utils.h" 27b68e73448104714e8f12f89a1e00fb10b5fd14c4Ken Wakasa#include "suggest/core/policy/traversal.h" 28b68e73448104714e8f12f89a1e00fb10b5fd14c4Ken Wakasa#include "suggest/core/session/dic_traverse_session.h" 29b68e73448104714e8f12f89a1e00fb10b5fd14c4Ken Wakasa#include "suggest/policyimpl/typing/scoring_params.h" 30addea83bad5751308fef508d79c6989b8872f050Ken Wakasa#include "utils/char_utils.h" 31a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 32a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataokanamespace latinime { 33a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataokaclass TypingTraversal : public Traversal { 34a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka public: 35a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka static const TypingTraversal *getInstance() { return &sInstance; } 36a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 37a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE int getMaxPointerCount() const { 38a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return MAX_POINTER_COUNT; 39a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 40a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 41a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool allowsErrorCorrections(const DicNode *const dicNode) const { 42a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return dicNode->getNormalizedSpatialDistance() 438da0c932925f605b9dd815387753dfab19beb873Tom Ouyang < ScoringParams::NORMALIZED_SPATIAL_DISTANCE_THRESHOLD_FOR_EDIT; 44a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 45a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 46a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool isOmission(const DicTraverseSession *const traverseSession, 47fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang const DicNode *const dicNode, const DicNode *const childDicNode, 48fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang const bool allowsErrorCorrections) const { 49a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka if (!CORRECT_OMISSION) { 50a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 51a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 52fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang // Note: Always consider intentional omissions (like apostrophes) since they are common. 53fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang const bool canConsiderOmission = 54fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang allowsErrorCorrections || childDicNode->canBeIntentionalOmission(); 55fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang if (!canConsiderOmission) { 56fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang return false; 57fd02ec10f0a0374096e88fa30a0e126d6ff11c72Tom Ouyang } 58a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int inputSize = traverseSession->getInputSize(); 59a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka // TODO: Don't refer to isCompletion? 60a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka if (dicNode->isCompletion(inputSize)) { 61a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 62a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 63a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka if (dicNode->canBeIntentionalOmission()) { 64a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return true; 65a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 66a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int point0Index = dicNode->getInputIndex(0); 67a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int currentBaseLowerCodePoint = 68464d3ba43257da34ab165da8ba0af11e928aae5cKen Wakasa CharUtils::toBaseLowerCase(childDicNode->getNodeCodePoint()); 69a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int typedBaseLowerCodePoint = 70464d3ba43257da34ab165da8ba0af11e928aae5cKen Wakasa CharUtils::toBaseLowerCase(traverseSession->getProximityInfoState(0) 71a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka ->getPrimaryCodePointAt(point0Index)); 72a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return (currentBaseLowerCodePoint != typedBaseLowerCodePoint); 73a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 74a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 75a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool isSpaceSubstitutionTerminal( 76a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const { 77252412d7eb4573f91588b06b0fe49ef9f0ac38acSatoshi Kataoka if (!CORRECT_NEW_WORD_SPACE_SUBSTITUTION) { 78a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 79a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 80a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka if (!canDoLookAheadCorrection(traverseSession, dicNode)) { 81a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 82a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 83a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int point0Index = dicNode->getInputIndex(0); 84a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return dicNode->isTerminalWordNode() 85a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka && traverseSession->getProximityInfoState(0)-> 86a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka hasSpaceProximity(point0Index); 87a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 88a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 89a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool isSpaceOmissionTerminal( 90a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const { 91252412d7eb4573f91588b06b0fe49ef9f0ac38acSatoshi Kataoka if (!CORRECT_NEW_WORD_SPACE_OMISSION) { 92a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 93a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 94a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int inputSize = traverseSession->getInputSize(); 95a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka // TODO: Don't refer to isCompletion? 96a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka if (dicNode->isCompletion(inputSize)) { 97a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 98a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 99a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka if (!dicNode->isTerminalWordNode()) { 100a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 101a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 102a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int16_t pointIndex = dicNode->getInputIndex(0); 103a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return pointIndex <= inputSize && !dicNode->isTotalInputSizeExceedingLimit() 1043e954347e3a7b381d7e94feb002e158f3bc69a32Jean Chalard && !dicNode->shouldBeFilteredBySafetyNetForBigram(); 105a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 106a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 107a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool shouldDepthLevelCache( 108a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession) const { 109a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int inputSize = traverseSession->getInputSize(); 110a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return traverseSession->isCacheBorderForTyping(inputSize); 111a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 112a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 113a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool shouldNodeLevelCache( 114a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const { 115a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 116a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 117a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 118a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool canDoLookAheadCorrection( 119a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const { 120a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int inputSize = traverseSession->getInputSize(); 121a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return dicNode->canDoLookAheadCorrection(inputSize); 122a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 123a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 124a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE ProximityType getProximityType( 125a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession, const DicNode *const dicNode, 126a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicNode *const childDicNode) const { 127a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return traverseSession->getProximityInfoState(0)->getProximityType( 128a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka dicNode->getInputIndex(0), childDicNode->getNodeCodePoint(), 129a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka true /* checkProximityChars */); 130a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 131a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 132a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool needsToTraverseAllUserInput() const { 133a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return true; 134a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 135a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 136a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE float getMaxSpatialDistance() const { 137a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return ScoringParams::MAX_SPATIAL_DISTANCE; 138a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 139a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 140a829188f54c0fd2e26192d98d9e56e033d8f91aaKeisuke Kuroynagi AK_FORCE_INLINE bool autoCorrectsToMultiWordSuggestionIfTop() const { 141a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return true; 142a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 143a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 144a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE int getDefaultExpandDicNodeSize() const { 145a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return DicNodeVector::DEFAULT_NODES_SIZE_FOR_OPTIMIZATION; 146a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 147a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 148a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool sameAsTyped( 149a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const { 150a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return traverseSession->getProximityInfoState(0)->sameAsTyped( 15109858cbde49c086dd1d4d3050b57f0ea1774158bKeisuke Kuroynagi dicNode->getOutputWordBuf(), dicNode->getNodeCodePointCount()); 152a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 153a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 1544447b14b785652e36adca329f5cddf986bfd14faKeisuke Kuroynagi AK_FORCE_INLINE int getMaxCacheSize(const int inputSize) const { 1554447b14b785652e36adca329f5cddf986bfd14faKeisuke Kuroynagi return (inputSize <= 1) ? ScoringParams::MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT 1564447b14b785652e36adca329f5cddf986bfd14faKeisuke Kuroynagi : ScoringParams::MAX_CACHE_DIC_NODE_SIZE; 157a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 158a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 159a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool isPossibleOmissionChildNode( 160a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicTraverseSession *const traverseSession, const DicNode *const parentDicNode, 161a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const DicNode *const dicNode) const { 162a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const ProximityType proximityType = 163a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka getProximityType(traverseSession, parentDicNode, dicNode); 1647a06a792871c38517264fcb63b80a9c09bfe4766Keisuke Kuroynagi if (!ProximityInfoUtils::isMatchOrProximityChar(proximityType)) { 165a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 166a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 167a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return true; 168a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 169a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 170a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka AK_FORCE_INLINE bool isGoodToTraverseNextWord(const DicNode *const dicNode) const { 171a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int probability = dicNode->getProbability(); 172a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka if (probability < ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY) { 173a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return false; 174a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 175a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka const int c = dicNode->getOutputWordBuf()[0]; 17609858cbde49c086dd1d4d3050b57f0ea1774158bKeisuke Kuroynagi const bool shortCappedWord = dicNode->getNodeCodePointCount() 177464d3ba43257da34ab165da8ba0af11e928aae5cKen Wakasa < ScoringParams::THRESHOLD_SHORT_WORD_LENGTH && CharUtils::isAsciiUpper(c); 178a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka return !shortCappedWord 179a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka || probability >= ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY_FOR_CAPPED; 180a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka } 181a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 182a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka private: 183a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka DISALLOW_COPY_AND_ASSIGN(TypingTraversal); 184a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka static const bool CORRECT_OMISSION; 185252412d7eb4573f91588b06b0fe49ef9f0ac38acSatoshi Kataoka static const bool CORRECT_NEW_WORD_SPACE_SUBSTITUTION; 186252412d7eb4573f91588b06b0fe49ef9f0ac38acSatoshi Kataoka static const bool CORRECT_NEW_WORD_SPACE_OMISSION; 187a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka static const TypingTraversal sInstance; 188a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka 189a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka TypingTraversal() {} 190a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka ~TypingTraversal() {} 191a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka}; 192a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka} // namespace latinime 193a6a416519603d2e65416dd8f9507913b7e4fd0a0Satoshi Kataoka#endif // LATINIME_TYPING_TRAVERSAL_H 194