Commit 0d21cf83 authored by Shu Chen's avatar Shu Chen Committed by Commit Bot

Makes the rule based engine handle the transform rules.

Bug: 859432
Change-Id: I61f8540a9027d43a29dcde5892f4547bad09c026
Reviewed-on: https://chromium-review.googlesource.com/c/1312143
Commit-Queue: Shu Chen <shuchen@chromium.org>
Reviewed-by: default avatarLeo Zhang <googleo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604845}
parent 46a9bbb1
......@@ -262,5 +262,69 @@ TEST_F(ImeServiceTest, RuleBasedArabic) {
EXPECT_EQ("{\"result\":false}", response);
}
// Tests that the rule-based DevaPhone keyboard can work correctly.
TEST_F(ImeServiceTest, RuleBasedDevaPhone) {
bool success = false;
TestClientChannel test_channel;
mojom::InputChannelPtr to_engine_ptr;
ime_manager_->ConnectToImeEngine(
"m17n:deva_phone", mojo::MakeRequest(&to_engine_ptr),
test_channel.CreateInterfacePtrAndBind(), extra,
base::BindOnce(&ConnectCallback, &success));
ime_manager_.FlushForTesting();
EXPECT_TRUE(success);
std::string response;
// KeyN.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyN\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
const char* expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"setComposition\","
u8"\"arguments\":[\"\u0928\"]}]}";
EXPECT_EQ(expected_response, response);
// Backspace.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"Backspace\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"setComposition\","
u8"\"arguments\":[\"\"]}]}";
EXPECT_EQ(expected_response, response);
// KeyN + KeyC.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyN\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyC\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"setComposition\","
u8"\"arguments\":[\"\u091e\u094d\u091a\"]}]}";
EXPECT_EQ(expected_response, response);
// Space.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"Space\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"commitText\","
u8"\"arguments\":[\"\u091e\u094d\u091a \"]}]}";
EXPECT_EQ(expected_response, response);
}
} // namespace ime
} // namespace chromeos
......@@ -143,19 +143,38 @@ std::string InputEngine::Process(const std::string& message,
// 'result': <boolean>,
// 'operations': [
// {
// 'method': 'commitText',
// 'method': 'commitText|setComposition',
// 'arguments': <string>
// }
// ]
// }
std::string response_str = "{\"result\":";
response_str += (res.key_handled ? "true" : "false");
if (res.commit_text.empty())
std::vector<std::string> ops;
if (!res.commit_text.empty()) {
ops.push_back("{\"method\":\"commitText\",\"arguments\":[\"" +
res.commit_text + "\"]}");
}
// Need to add the setComposition operation to the result when the key is
// handled and commit_text and composition_text are both empty.
// That is the case of using Backspace to delete the last character in
// composition.
if (!res.composition_text.empty() ||
(res.key_handled && res.commit_text.empty())) {
ops.push_back("{\"method\":\"setComposition\",\"arguments\":[\"" +
res.composition_text + "\"]}");
}
if (ops.empty()) {
response_str += "}";
else
response_str +=
",\"operations\":[{\"method\":\"commitText\",\"arguments\":[\"" +
res.commit_text + "\"]}]}";
} else {
response_str += ",\"operations\":[";
for (size_t i = 0; i < ops.size(); ++i) {
if (i > 0)
response_str += ",";
response_str += ops[i];
}
response_str += "]}";
}
return response_str;
}
......
......@@ -4,6 +4,7 @@
#include "chromeos/services/ime/public/cpp/rulebased/engine.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/services/ime/public/cpp/rulebased/rules_data.h"
namespace chromeos {
......@@ -27,8 +28,13 @@ void Engine::Activate(const std::string& id) {
}
void Engine::Reset() {
// TODO(shuchen): Implement this for the ones with transform rules.
process_key_count_ = 0;
// Clears current state.
context_ = "";
transat_ = -1;
ClearHistory();
}
ProcessKeyResult Engine::ProcessKey(const std::string& code,
......@@ -36,6 +42,8 @@ ProcessKeyResult Engine::ProcessKey(const std::string& code,
process_key_count_++;
ProcessKeyResult res;
// The fallback result should commit the existing composition text.
res.commit_text = context_;
if (!current_data_)
return res;
if (modifier_state > 7)
......@@ -43,11 +51,98 @@ ProcessKeyResult Engine::ProcessKey(const std::string& code,
const KeyMap* key_map = current_data_->GetKeyMapByModifiers(modifier_state);
auto it = key_map->find(code);
if (it == key_map->end())
if (it == key_map->end()) {
if (code == "Backspace" && !context_.empty())
return ProcessBackspace();
return res;
}
res.key_handled = true;
const std::string& key_char = it->second;
if (!current_data_->HasTransform()) {
res.commit_text = key_char;
return res;
}
if (code == "Space") {
res.commit_text = context_ + key_char;
Reset();
return res;
}
// Deals with the transforms.
std::string composition;
// If history exists, use history to match first.
// Otherwise, use current state to match.
bool matched = false;
if (!history_ambi_.empty()) {
matched = current_data_->Transform(history_context_, history_transat_,
history_ambi_ + key_char, &composition);
}
if (!matched)
matched =
current_data_->Transform(context_, transat_, key_char, &composition);
// Updates history as necessary before changing the current state.
if (current_data_->MatchHistoryPrune(history_ambi_ + key_char)) {
if (history_ambi_.empty()) { // First time?
history_context_ = context_;
history_transat_ = transat_;
history_ambi_ = key_char;
} else {
history_ambi_ += key_char;
}
} else if (current_data_->MatchHistoryPrune(key_char)) {
history_context_ = context_;
history_transat_ = transat_;
history_ambi_ = key_char;
} else {
ClearHistory();
}
// Changing the current state.
if (matched) {
context_ = composition;
transat_ = composition.size();
} else {
context_ += key_char;
}
// Returns the result according to the current state.
res.composition_text = context_;
res.commit_text = "";
return res;
}
// private
void Engine::ClearHistory() {
history_context_ = "";
history_transat_ = -1;
history_ambi_ = "";
}
ProcessKeyResult Engine::ProcessBackspace() {
ProcessKeyResult res;
// Reverts the current state.
// If the backspace across over the transat pos, adjusts it.
base::string16 text = base::UTF8ToUTF16(context_);
text = text.substr(0, text.length() - 1);
context_ = base::UTF16ToUTF8(text);
if (transat_ > (int)context_.length())
transat_ = context_.length();
// Reverts the history state, clears it if necessary.
if (!history_ambi_.empty()) {
text = base::UTF8ToUTF16(history_ambi_);
text = text.substr(0, text.length() - 1);
history_ambi_ = base::UTF16ToUTF8(text);
if (history_ambi_.empty())
ClearHistory();
}
res.key_handled = true;
res.commit_text = it->second;
res.composition_text = context_;
return res;
}
......
......@@ -26,6 +26,7 @@ enum Modifiers {
struct ProcessKeyResult {
bool key_handled = false;
std::string commit_text;
std::string composition_text;
};
class Engine {
......@@ -42,10 +43,28 @@ class Engine {
uint32_t process_key_count() const { return process_key_count_; }
private:
void ClearHistory();
ProcessKeyResult ProcessBackspace();
std::unique_ptr<const RulesData> current_data_;
std::string current_id_;
uint32_t process_key_count_;
// Current state.
// The current context (composition).
std::string context_;
// The current transat position.
// Refers to RulesData::Transform for details about transat.
int transat_ = -1;
// History state.
// The history context, before entering ambiguous transforms.
std::string history_context_;
// The history transat, before entering ambiguous transforms.
int history_transat_ = -1;
// The history ambiguous string which matches the history prune regexp.
std::string history_ambi_;
DISALLOW_COPY_AND_ASSIGN(Engine);
};
......
......@@ -16,7 +16,8 @@ namespace {
struct KeyVerifyEntry {
const char* key;
uint8_t modifiers;
const char* expected_str;
const char* expected_commit_text;
const char* expected_composition_text;
};
} // namespace
......@@ -34,7 +35,8 @@ class RulebasedImeTest : public testing::Test {
rulebased::ProcessKeyResult res =
engine_->ProcessKey(entry.key, entry.modifiers);
EXPECT_TRUE(res.key_handled);
EXPECT_EQ(entry.expected_str, res.commit_text);
EXPECT_EQ(entry.expected_commit_text, res.commit_text);
EXPECT_EQ(entry.expected_composition_text, res.composition_text);
}
}
......@@ -47,38 +49,97 @@ class RulebasedImeTest : public testing::Test {
TEST_F(RulebasedImeTest, Arabic) {
engine_->Activate("ar");
std::vector<KeyVerifyEntry> entries;
entries.push_back({"KeyA", rulebased::MODIFIER_SHIFT, u8"\u0650"});
entries.push_back({"KeyB", 0, u8"\u0644\u0627"});
entries.push_back({"Space", 0, " "});
entries.push_back({"KeyA", rulebased::MODIFIER_SHIFT, u8"\u0650", ""});
entries.push_back({"KeyB", 0, u8"\u0644\u0627", ""});
entries.push_back({"Space", 0, " ", ""});
VerifyKeys(entries);
}
TEST_F(RulebasedImeTest, Persian) {
engine_->Activate("fa");
std::vector<KeyVerifyEntry> entries;
entries.push_back({"KeyA", 0, u8"\u0634"});
entries.push_back({"KeyV", rulebased::MODIFIER_SHIFT, ""});
entries.push_back({"Space", rulebased::MODIFIER_SHIFT, u8"\u200c"});
entries.push_back({"KeyA", 0, u8"\u0634", ""});
entries.push_back({"KeyV", rulebased::MODIFIER_SHIFT, "", ""});
entries.push_back({"Space", rulebased::MODIFIER_SHIFT, u8"\u200c", ""});
VerifyKeys(entries);
}
TEST_F(RulebasedImeTest, Thai) {
engine_->Activate("th");
std::vector<KeyVerifyEntry> entries;
entries.push_back({"KeyA", 0, u8"\u0e1f"});
entries.push_back({"KeyA", rulebased::MODIFIER_ALTGR, ""});
entries.push_back({"KeyA", 0, u8"\u0e1f", ""});
entries.push_back({"KeyA", rulebased::MODIFIER_ALTGR, "", ""});
VerifyKeys(entries);
engine_->Activate("th_pattajoti");
entries.clear();
entries.push_back({"KeyA", 0, u8"\u0e49"});
entries.push_back({"KeyB", rulebased::MODIFIER_SHIFT, u8"\u0e31\u0e49"});
entries.push_back({"KeyA", 0, u8"\u0e49", ""});
entries.push_back({"KeyB", rulebased::MODIFIER_SHIFT, u8"\u0e31\u0e49", ""});
VerifyKeys(entries);
engine_->Activate("th_tis");
entries.clear();
entries.push_back({"KeyA", 0, u8"\u0e1f"});
entries.push_back({"KeyM", rulebased::MODIFIER_SHIFT, u8"?"});
entries.push_back({"KeyA", 0, u8"\u0e1f", ""});
entries.push_back({"KeyM", rulebased::MODIFIER_SHIFT, u8"?", ""});
VerifyKeys(entries);
}
TEST_F(RulebasedImeTest, DevaPhone) {
engine_->Activate("deva_phone");
std::vector<KeyVerifyEntry> entries;
// "njnchh" -> "\u091e\u094d\u091c\u091e\u094d\u091b".
entries.push_back({"KeyN", 0, "", u8"\u0928"});
entries.push_back({"KeyJ", 0, "", u8"\u091e\u094d\u091c"});
entries.push_back({"KeyN", 0, "", u8"\u091e\u094d\u091c\u094d\u091e"});
entries.push_back({"KeyC", 0, "", u8"\u091e\u094d\u091c\u091e\u094d\u091a"});
entries.push_back({"KeyH", 0, "", u8"\u091e\u094d\u091c\u091e\u094d\u091a"});
entries.push_back({"KeyH", 0, "", u8"\u091e\u094d\u091c\u091e\u094d\u091b"});
entries.push_back({"Backspace", 0, "", u8"\u091e\u094d\u091c\u091e\u094d"});
entries.push_back({"Space", 0, u8"\u091e\u094d\u091c\u091e\u094d ", ""});
VerifyKeys(entries);
}
TEST_F(RulebasedImeTest, DevaPhone_Backspace) {
engine_->Activate("deva_phone");
std::vector<KeyVerifyEntry> entries;
entries.push_back({"KeyN", 0, "", u8"\u0928"});
entries.push_back({"Backspace", 0, "", u8""});
entries.push_back({"KeyN", 0, "", u8"\u0928"});
entries.push_back({"KeyC", 0, "", u8"\u091e\u094d\u091a"});
entries.push_back({"Backspace", 0, "", u8"\u091e\u094d"});
entries.push_back({"KeyC", 0, "", u8"\u091e\u094d\u091a"});
entries.push_back({"KeyH", 0, "", u8"\u091e\u094d\u091a"});
entries.push_back({"Backspace", 0, "", u8"\u091e\u094d"});
entries.push_back({"KeyH", 0, "", u8"\u091e\u094d\u091a"});
entries.push_back({"Backspace", 0, "", u8"\u091e\u094d"});
entries.push_back({"Backspace", 0, "", u8"\u091e"});
entries.push_back({"Backspace", 0, "", u8""});
VerifyKeys(entries);
}
TEST_F(RulebasedImeTest, DevaPhone_Enter) {
engine_->Activate("deva_phone");
std::vector<KeyVerifyEntry> entries;
entries.push_back({"KeyN", 0, "", u8"\u0928"});
VerifyKeys(entries);
rulebased::ProcessKeyResult res = engine_->ProcessKey("Enter", 0);
EXPECT_FALSE(res.key_handled);
EXPECT_EQ(u8"\u0928", res.commit_text);
EXPECT_EQ("", res.composition_text);
}
TEST_F(RulebasedImeTest, DevaPhone_Reset) {
engine_->Activate("deva_phone");
std::vector<KeyVerifyEntry> entries;
entries.push_back({"KeyN", 0, "", u8"\u0928"});
VerifyKeys(entries);
engine_->Reset();
entries.clear();
entries.push_back({"KeyC", 0, "", u8"\u091a"});
VerifyKeys(entries);
}
......
......@@ -334,6 +334,10 @@ bool RulesData::Transform(const std::string& context,
return true;
}
bool RulesData::MatchHistoryPrune(const std::string& str) const {
return re2::RE2::FullMatch(str, *history_prune_re_);
}
} // namespace rulebased
} // namespace ime
} // namespace chromeos
......@@ -56,7 +56,9 @@ class RulesData {
const std::string& appended,
std::string* transformed) const;
const re2::RE2* history_prune_re() const { return history_prune_re_.get(); }
bool HasTransform() const { return transform_re_merged_.get(); }
bool MatchHistoryPrune(const std::string& str) const;
private:
// The KeyMap instances under all the modifier states.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment