1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "rewriter/calculator_rewriter.h"
31 
32 #include <algorithm>
33 #include <string>
34 
35 #include "base/logging.h"
36 #include "base/util.h"
37 #include "config/config_handler.h"
38 #include "converter/converter_interface.h"
39 #include "converter/segments.h"
40 #include "protocol/commands.pb.h"
41 #include "protocol/config.pb.h"
42 #include "request/conversion_request.h"
43 #include "rewriter/calculator/calculator_interface.h"
44 
45 namespace mozc {
46 
CalculatorRewriter(const ConverterInterface * parent_converter)47 CalculatorRewriter::CalculatorRewriter(
48     const ConverterInterface *parent_converter)
49     : parent_converter_(parent_converter) {
50   DCHECK(parent_converter_);
51 }
52 
~CalculatorRewriter()53 CalculatorRewriter::~CalculatorRewriter() {}
54 
capability(const ConversionRequest & request) const55 int CalculatorRewriter::capability(const ConversionRequest &request) const {
56   if (request.request().mixed_conversion()) {
57     return RewriterInterface::ALL;
58   }
59   return RewriterInterface::CONVERSION;
60 }
61 
62 // Rewrites candidates when conversion segments of |segments| represents an
63 // expression that can be calculated. In such case, if |segments| consists
64 // of multiple segments, it merges them by calling ConverterInterface::
65 // ResizeSegment(), otherwise do calculation and insertion.
66 // TODO(tok): It currently calculates same expression twice, if |segments| is
67 //            a valid expression.
Rewrite(const ConversionRequest & request,Segments * segments) const68 bool CalculatorRewriter::Rewrite(const ConversionRequest &request,
69                                  Segments *segments) const {
70   if (!request.config().use_calculator()) {
71     return false;
72   }
73 
74   CalculatorInterface *calculator = CalculatorFactory::GetCalculator();
75 
76   const size_t segments_size = segments->conversion_segments_size();
77   if (segments_size == 0) {
78     return false;
79   }
80 
81   // If |segments| has only one conversion segment, try calculation and insert
82   // the result on success.
83   if (segments_size == 1) {
84     const string &key = segments->conversion_segment(0).key();
85     string result;
86     if (key.empty()) {
87       return false;
88     }
89     if (!calculator->CalculateString(key, &result)) {
90       return false;
91     }
92     // Insert the result.
93     if (!InsertCandidate(result, 0, segments->mutable_conversion_segment(0))) {
94       return false;
95     }
96     return true;
97   }
98 
99   // Merge keys of all conversion segments and try calculation.
100   string merged_key;
101   for (size_t i = 0; i < segments->conversion_segments_size(); ++i) {
102     merged_key += segments->conversion_segment(i).key();
103   }
104   // The decision to calculate and calculation itself are both done by the
105   // calculator.
106   string result;
107   if (!calculator->CalculateString(merged_key, &result)) {
108     return false;
109   }
110 
111   // Merge all conversion segments.
112   int offset = Util::CharsLen(merged_key) -
113                    Util::CharsLen(segments->conversion_segment(0).key());
114   // ConverterInterface::ResizeSegment() calls Rewriter::Rewrite(), so
115   // CalculatorRewriter::Rewrite() is recursively called with merged
116   // conversion segment.
117   if (!parent_converter_->ResizeSegment(segments, request, 0, offset)) {
118     LOG(ERROR) << "Failed to merge conversion segments";
119     return false;
120   }
121   return true;
122 }
123 
InsertCandidate(const string & value,size_t insert_pos,Segment * segment) const124 bool CalculatorRewriter::InsertCandidate(const string &value,
125                                          size_t insert_pos,
126                                          Segment *segment) const {
127   if (segment->candidates_size() == 0) {
128     LOG(WARNING) << "candidates_size is 0";
129     return false;
130   }
131 
132   const Segment::Candidate &base_candidate = segment->candidate(0);
133 
134   // Normalize the expression, used in description.
135   string temp, temp2, expression;
136   Util::FullWidthAsciiToHalfWidthAscii(base_candidate.content_key, &temp);
137   Util::StringReplace(temp, "・", "/", true, &temp2);
138   // "ー", onbiki
139   Util::StringReplace(temp2, "ー", "-", true, &expression);
140 
141   size_t offset = std::min(insert_pos, segment->candidates_size());
142 
143   for (int n = 0; n < 2; ++n) {
144     int current_offset = offset + n;
145     Segment::Candidate *candidate = segment->insert_candidate(
146         current_offset);
147     if (candidate == NULL) {
148       LOG(ERROR) << "cannot insert candidate at " << current_offset;
149       return false;
150     }
151 
152     // Simply sets some member variables of the new candidate to ones of the
153     // existing candidate next to it.
154     size_t reference_index = current_offset + 1;
155     if (reference_index >= segment->candidates_size()) {
156       reference_index = current_offset - 1;
157     }
158     const Segment::Candidate &reference_candidate =
159         segment->candidate(reference_index);
160 
161     candidate->Init();
162     candidate->lid = reference_candidate.lid;
163     candidate->rid = reference_candidate.rid;
164     candidate->cost = reference_candidate.cost;
165     candidate->key = base_candidate.key;
166     candidate->content_key = base_candidate.content_key;
167     candidate->attributes |= Segment::Candidate::NO_VARIANTS_EXPANSION;
168     candidate->attributes |= Segment::Candidate::NO_LEARNING;
169     candidate->description = "計算結果";
170 
171     if (n == 0) {   // without expression
172       candidate->value = value;
173       candidate->content_value = value;
174     } else {       // with expression
175       DCHECK(!expression.empty());
176       if (expression.front() == '=') {
177         // Expression starts with '='.
178         // Appends value to the left of expression.
179         candidate->value = value + expression;
180         candidate->content_value = value + expression;
181       } else {
182         // Expression ends with '='.
183         // Appends value to the right of expression.
184         candidate->value = expression + value;
185         candidate->content_value = expression + value;
186       }
187     }
188   }
189 
190   return true;
191 }
192 }  // namespace mozc
193