diff --git a/src/rime/algo/utilities.cc b/src/rime/algo/utilities.cc index ea1a2302b4..3ccb1fcedb 100644 --- a/src/rime/algo/utilities.cc +++ b/src/rime/algo/utilities.cc @@ -30,6 +30,9 @@ int CompareVersionString(const string& x, const string& y) { return 0; } +ChecksumComputer::ChecksumComputer(uint32_t initial_remainder) + : crc_(initial_remainder) {} + void ChecksumComputer::ProcessFile(const string& file_name) { std::ifstream fin(file_name.c_str()); string file_content((std::istreambuf_iterator(fin)), diff --git a/src/rime/algo/utilities.h b/src/rime/algo/utilities.h index 132bdd5f50..4231b16b86 100644 --- a/src/rime/algo/utilities.h +++ b/src/rime/algo/utilities.h @@ -18,6 +18,7 @@ int CompareVersionString(const string& x, class ChecksumComputer { public: + explicit ChecksumComputer(uint32_t initial_remainder = 0); void ProcessFile(const string& file_name); uint32_t Checksum(); diff --git a/src/rime/dict/dict_compiler.cc b/src/rime/dict/dict_compiler.cc index 1ec0495031..61554d824b 100644 --- a/src/rime/dict/dict_compiler.cc +++ b/src/rime/dict/dict_compiler.cc @@ -26,85 +26,103 @@ namespace rime { DictCompiler::DictCompiler(Dictionary *dictionary, const string& prefix) : dict_name_(dictionary->name()), + packs_(dictionary->packs()), prism_(dictionary->prism()), - table_(dictionary->table()), + tables_(dictionary->tables()), prefix_(prefix) { } -static string LocateFile(const string& file_name) { +static string locate_file(const string& file_name) { the resolver( Service::instance().CreateResourceResolver({"build_source", "", ""})); return resolver->ResolvePath(file_name).string(); } +static bool load_dict_settings_from_file(DictSettings* settings, + const string& dict_file) { + std::ifstream fin(dict_file.c_str()); + bool success = settings->LoadDictHeader(fin); + fin.close(); + return success; +} + +static bool get_dict_files_from_settings(vector* dict_files, + DictSettings& settings) { + if (auto tables = settings.GetTables()) { + for(auto it = tables->begin(); it != tables->end(); ++it) { + string dict_name = As(*it)->str(); + string dict_file = locate_file(dict_name + ".dict.yaml"); + if (!boost::filesystem::exists(dict_file)) { + LOG(ERROR) << "source file '" << dict_file << "' does not exist."; + return false; + } + dict_files->push_back(dict_file); + } + } + return true; +} + +static uint32_t compute_dict_file_checksum(uint32_t initial_checksum, + const vector& dict_files, + DictSettings& settings) { + if (dict_files.empty()) { + return initial_checksum; + } + ChecksumComputer cc(initial_checksum); + for (const auto& file_name : dict_files) { + cc.ProcessFile(file_name); + } + if (settings.use_preset_vocabulary()) { + cc.ProcessFile(PresetVocabulary::DictFilePath(settings.vocabulary())); + } + return cc.Checksum(); +} + bool DictCompiler::Compile(const string &schema_file) { LOG(INFO) << "compiling dictionary for " << schema_file; bool build_table_from_source = true; DictSettings settings; - string dict_file = LocateFile(dict_name_ + ".dict.yaml"); + string dict_file = locate_file(dict_name_ + ".dict.yaml"); if (!boost::filesystem::exists(dict_file)) { LOG(ERROR) << "source file '" << dict_file << "' does not exist."; build_table_from_source = false; } - else { - std::ifstream fin(dict_file.c_str()); - if (!settings.LoadDictHeader(fin)) { - LOG(ERROR) << "failed to load settings from '" << dict_file << "'."; - return false; - } - fin.close(); - LOG(INFO) << "dict name: " << settings.dict_name(); - LOG(INFO) << "dict version: " << settings.dict_version(); + else if (!load_dict_settings_from_file(&settings, dict_file)) { + LOG(ERROR) << "failed to load settings from '" << dict_file << "'."; + return false; } vector dict_files; - auto tables = settings.GetTables(); - for(auto it = tables->begin(); it != tables->end(); ++it) { - if (!Is(*it)) - continue; - string dict_name = As(*it)->str(); - string dict_file = LocateFile(dict_name + ".dict.yaml"); - if (!boost::filesystem::exists(dict_file)) { - LOG(ERROR) << "source file '" << dict_file << "' does not exist."; - return false; - } - dict_files.push_back(dict_file); - } - uint32_t dict_file_checksum = 0; - if (!dict_files.empty()) { - ChecksumComputer cc; - for (const auto& file_name : dict_files) { - cc.ProcessFile(file_name); - } - if (settings.use_preset_vocabulary()) { - cc.ProcessFile(PresetVocabulary::DictFilePath(settings.vocabulary())); - } - dict_file_checksum = cc.Checksum(); + if (!get_dict_files_from_settings(&dict_files, settings)) { + return false; } + uint32_t dict_file_checksum = + compute_dict_file_checksum(0, dict_files, settings); uint32_t schema_file_checksum = schema_file.empty() ? 0 : Checksum(schema_file); - bool rebuild_table = true; - bool rebuild_prism = true; - if (table_->Exists() && table_->Load()) { - if (!build_table_from_source) { - dict_file_checksum = table_->dict_file_checksum(); - LOG(INFO) << "reuse existing table: " << table_->file_name(); - } - if (table_->dict_file_checksum() == dict_file_checksum) { - rebuild_table = false; + bool rebuild_table = false; + bool rebuild_prism = false; + const auto& primary_table = tables_[0]; + if (primary_table->Exists() && primary_table->Load()) { + if (build_table_from_source) { + rebuild_table = primary_table->dict_file_checksum() != dict_file_checksum; + } else { + dict_file_checksum = primary_table->dict_file_checksum(); + LOG(INFO) << "reuse existing table: " << primary_table->file_name(); } - table_->Close(); - } - else if (!build_table_from_source) { + primary_table->Close(); + } else if (build_table_from_source) { + rebuild_table = true; + } else { LOG(ERROR) << "neither " << dict_name_ << ".dict.yaml nor " << dict_name_ << ".table.bin exists."; return false; } if (prism_->Exists() && prism_->Load()) { - if (prism_->dict_file_checksum() == dict_file_checksum && - prism_->schema_file_checksum() == schema_file_checksum) { - rebuild_prism = false; - } + rebuild_prism = prism_->dict_file_checksum() != dict_file_checksum || + prism_->schema_file_checksum() != schema_file_checksum; prism_->Close(); + } else { + rebuild_prism = true; } LOG(INFO) << dict_file << "[" << dict_files.size() << " file(s)]" << " (" << dict_file_checksum << ")"; @@ -126,11 +144,55 @@ bool DictCompiler::Compile(const string &schema_file) { if (options_ & kRebuildPrism) { rebuild_prism = true; } - if (rebuild_table && !BuildTable(&settings, dict_files, dict_file_checksum)) - return false; - if (rebuild_prism && !BuildPrism(schema_file, - dict_file_checksum, schema_file_checksum)) + Syllabary syllabary; + if (rebuild_table) { + EntryCollector collector; + if (!BuildTable(0, + collector, + &settings, + dict_files, + dict_file_checksum)) { + return false; + } + syllabary = std::move(collector.syllabary); + } + if (rebuild_prism && + !BuildPrism(schema_file, + syllabary, + dict_file_checksum, + schema_file_checksum)) { return false; + } + if (rebuild_table) { + for (int table_index = 1; table_index < tables_.size(); ++table_index) { + const auto& pack_name = packs_[table_index - 1]; + EntryCollector collector(std::move(syllabary)); + DictSettings settings; + string dict_file = locate_file(pack_name + ".dict.yaml"); + if (!boost::filesystem::exists(dict_file)) { + LOG(ERROR) << "source file '" << dict_file << "' does not exist."; + continue; + } + if (!load_dict_settings_from_file(&settings, dict_file)) { + LOG(ERROR) << "failed to load settings from '" << dict_file << "'."; + continue; + } + vector dict_files; + if (!get_dict_files_from_settings(&dict_files, settings)) { + continue; + } + uint32_t pack_file_checksum = + compute_dict_file_checksum(dict_file_checksum, dict_files, settings); + if (!BuildTable(table_index, + collector, + &settings, + dict_files, + pack_file_checksum)) { + LOG(ERROR) << "failed to build pack: " << pack_name; + } + syllabary = std::move(collector.syllabary); + } + } // done! return true; } @@ -143,17 +205,20 @@ static string RelocateToUserDirectory(const string& prefix, return resolver.ResolvePath(resource_id).string(); } -bool DictCompiler::BuildTable(DictSettings* settings, +bool DictCompiler::BuildTable(int table_index, + EntryCollector& collector, + DictSettings* settings, const vector& dict_files, uint32_t dict_file_checksum) { - LOG(INFO) << "building table..."; - table_ = New(RelocateToUserDirectory(prefix_, table_->file_name())); + auto& table = tables_[table_index]; + auto path = RelocateToUserDirectory(prefix_, table->file_name()); + LOG(INFO) << "building table: " << path; + table = New
(path); - EntryCollector collector; collector.Configure(settings); collector.Collect(dict_files); if (options_ & kDump) { - boost::filesystem::path path(table_->file_name()); + boost::filesystem::path path(table->file_name()); path.replace_extension(".txt"); collector.Dump(path.string()); } @@ -184,16 +249,34 @@ bool DictCompiler::BuildTable(DictSettings* settings, if (settings->sort_order() != "original") { vocabulary.SortHomophones(); } - table_->Remove(); - if (!table_->Build(collector.syllabary, vocabulary, collector.num_entries, - dict_file_checksum) || - !table_->Save()) { + table->Remove(); + if (!table->Build(collector.syllabary, + vocabulary, + collector.num_entries, + dict_file_checksum) || + !table->Save()) { return false; } } + // build reverse db for the primary table + if (table_index == 0 && + !BuildReverseDb(settings, + collector, + vocabulary, + dict_file_checksum)) { + return false; + } + return true; +} + +bool DictCompiler::BuildReverseDb(DictSettings* settings, + const EntryCollector& collector, + const Vocabulary& vocabulary, + uint32_t dict_file_checksum) { // build .reverse.bin - ReverseDb reverse_db(RelocateToUserDirectory(prefix_, - dict_name_ + ".reverse.bin")); + auto path = RelocateToUserDirectory(prefix_, + dict_name_ + ".reverse.bin"); + ReverseDb reverse_db(path); if (!reverse_db.Build(settings, collector.syllabary, vocabulary, @@ -206,15 +289,12 @@ bool DictCompiler::BuildTable(DictSettings* settings, } bool DictCompiler::BuildPrism(const string &schema_file, + const Syllabary& syllabary, uint32_t dict_file_checksum, uint32_t schema_file_checksum) { LOG(INFO) << "building prism..."; prism_ = New(RelocateToUserDirectory(prefix_, prism_->file_name())); - // get syllabary from table - Syllabary syllabary; - if (!table_->Load() || !table_->GetSyllabary(&syllabary) || syllabary.empty()) - return false; // apply spelling algebra and prepare corrections (if enabled) Script script; if (!schema_file.empty()) { diff --git a/src/rime/dict/dict_compiler.h b/src/rime/dict/dict_compiler.h index b0bbff63d1..0b7332a1db 100644 --- a/src/rime/dict/dict_compiler.h +++ b/src/rime/dict/dict_compiler.h @@ -18,6 +18,8 @@ class Table; class ReverseDb; class DictSettings; class EditDistanceCorrector; +class EntryCollector; +class Vocabulary; class DictCompiler { public: @@ -34,18 +36,25 @@ class DictCompiler { void set_options(int options) { options_ = options; } private: - bool BuildTable(DictSettings* settings, + bool BuildTable(int table_index, + EntryCollector& collector, + DictSettings* settings, const vector& dict_files, uint32_t dict_file_checksum); bool BuildPrism(const string& schema_file, + const Syllabary& syllabary, uint32_t dict_file_checksum, uint32_t schema_file_checksum); - bool BuildReverseLookupDict(ReverseDb* db, uint32_t dict_file_checksum); + bool BuildReverseDb(DictSettings* settings, + const EntryCollector& collector, + const Vocabulary& vocabulary, + uint32_t dict_file_checksum); - string dict_name_; + const string& dict_name_; + const vector& packs_; an prism_; an correction_; - an
table_; + vector> tables_; int options_ = 0; string prefix_; }; diff --git a/src/rime/dict/dict_settings.cc b/src/rime/dict/dict_settings.cc index 4464247b8d..8ad18dad8d 100644 --- a/src/rime/dict/dict_settings.cc +++ b/src/rime/dict/dict_settings.cc @@ -37,6 +37,10 @@ bool DictSettings::LoadDictHeader(std::istream& stream) { return true; } +bool DictSettings::empty() { + return (*this)["name"].IsNull(); +} + string DictSettings::dict_name() { return (*this)["name"].ToString(); } @@ -74,6 +78,8 @@ double DictSettings::min_phrase_weight() { } an DictSettings::GetTables() { + if (empty()) + return nullptr; auto tables = New(); tables->Append((*this)["name"]); auto imports = (*this)["import_tables"].AsList(); diff --git a/src/rime/dict/dict_settings.h b/src/rime/dict/dict_settings.h index 48973cbd9b..3da7216f6c 100644 --- a/src/rime/dict/dict_settings.h +++ b/src/rime/dict/dict_settings.h @@ -17,6 +17,7 @@ class DictSettings : public Config { public: DictSettings(); bool LoadDictHeader(std::istream& stream); + bool empty(); string dict_name(); string dict_version(); string sort_order(); diff --git a/src/rime/dict/dictionary.cc b/src/rime/dict/dictionary.cc index 068da589fe..cdd99a6e7b 100644 --- a/src/rime/dict/dictionary.cc +++ b/src/rime/dict/dictionary.cc @@ -141,27 +141,28 @@ bool DictEntryIterator::Skip(size_t num_entries) { // Dictionary members -Dictionary::Dictionary(const string& name, - an
table, +Dictionary::Dictionary(string name, + vector packs, + vector> tables, an prism) - : name_(name), table_(std::move(table)), prism_(std::move(prism)) { -} + : name_(name), + packs_(std::move(packs)), + tables_(std::move(tables)), + prism_(std::move(prism)) {} Dictionary::~Dictionary() { // should not close shared table and prism objects } -an -Dictionary::Lookup(const SyllableGraph& syllable_graph, - size_t start_pos, - double initial_credibility) { - if (!loaded()) - return nullptr; +static void lookup_table(Table* table, + DictEntryCollector* collector, + const SyllableGraph& syllable_graph, + size_t start_pos, + double initial_credibility) { TableQueryResult result; - if (!table_->Query(syllable_graph, start_pos, &result)) { - return nullptr; + if (!table->Query(syllable_graph, start_pos, &result)) { + return; } - auto collector = New(); // copy result for (auto& v : result) { size_t end_pos = v.first; @@ -173,15 +174,32 @@ Dictionary::Lookup(const SyllableGraph& syllable_graph, a.extra_code(), 0, syllable_graph, end_pos); if (actual_end_pos == 0) continue; (*collector)[actual_end_pos].AddChunk( - {table_.get(), a.code(), a.entry(), cr}); + {table, a.code(), a.entry(), cr}); } while (a.Next()); } else { - (*collector)[end_pos].AddChunk({table_.get(), a, cr}); + (*collector)[end_pos].AddChunk({table, a, cr}); } } } +} + +an +Dictionary::Lookup(const SyllableGraph& syllable_graph, + size_t start_pos, + double initial_credibility) { + if (!loaded()) + return nullptr; + auto collector = New(); + for (const auto& table : tables_) { + if (!table->IsOpen()) + continue; + lookup_table(table.get(), collector.get(), + syllable_graph, start_pos, initial_credibility); + } + if (collector->empty()) + return nullptr; // sort each group of equal code length for (auto& v : *collector) { v.second.Sort(); @@ -217,14 +235,18 @@ size_t Dictionary::LookupWords(DictEntryIterator* result, if (type > kNormalSpelling) continue; string remaining_code; if (match.length > code_length) { - string syllable = table_->GetSyllableById(syllable_id); + string syllable = primary_table()->GetSyllableById(syllable_id); if (syllable.length() > code_length) remaining_code = syllable.substr(code_length); } - TableAccessor a(table_->QueryWords(syllable_id)); - if (!a.exhausted()) { - DLOG(INFO) << "remaining code: " << remaining_code; - result->AddChunk({table_.get(), a, remaining_code}); + for (const auto& table : tables_) { + if (!table->IsOpen()) + continue; + TableAccessor a = table->QueryWords(syllable_id); + if (!a.exhausted()) { + DLOG(INFO) << "remaining code: " << remaining_code; + result->AddChunk({table.get(), a, remaining_code}); + } } } } @@ -232,11 +254,11 @@ size_t Dictionary::LookupWords(DictEntryIterator* result, } bool Dictionary::Decode(const Code& code, vector* result) { - if (!result || !table_) + if (!result || tables_.empty()) return false; result->clear(); for (SyllableId c : code) { - string s = table_->GetSyllableById(c); + string s = primary_table()->GetSyllableById(c); if (s.empty()) return false; result->push_back(s); @@ -246,19 +268,28 @@ bool Dictionary::Decode(const Code& code, vector* result) { bool Dictionary::Exists() const { return boost::filesystem::exists(prism_->file_name()) && - boost::filesystem::exists(table_->file_name()); + !tables_.empty() && + boost::filesystem::exists(tables_[0]->file_name()); } bool Dictionary::Remove() { if (loaded()) return false; prism_->Remove(); - table_->Remove(); + for (const auto& table : tables_) { + table->Remove(); + } return true; } bool Dictionary::Load() { LOG(INFO) << "loading dictionary '" << name_ << "'."; - if (!table_ || (!table_->IsOpen() && !table_->Load())) { + if (tables_.empty()) { + LOG(ERROR) << "Cannnot load dictionary '" << name_ + << "'; it contains no tables."; + return false; + } + auto& primary_table = tables_[0]; + if (!primary_table || (!primary_table->IsOpen() && !primary_table->Load())) { LOG(ERROR) << "Error loading table for dictionary '" << name_ << "'."; return false; } @@ -266,11 +297,19 @@ bool Dictionary::Load() { LOG(ERROR) << "Error loading prism for dictionary '" << name_ << "'."; return false; } + // packs are optional + for (int i = 1; i < tables_.size(); ++i) { + const auto& table = tables_[i]; + if (!table->IsOpen() && table->Exists() && table->Load()) { + LOG(INFO) << "loaded pack: " << packs_[i - 1]; + } + } return true; } bool Dictionary::loaded() const { - return table_ && table_->IsOpen() && prism_ && prism_->IsOpen(); + return !tables_.empty() && tables_[0]->IsOpen() && + prism_ && prism_->IsOpen(); } // DictionaryComponent members @@ -308,24 +347,46 @@ Dictionary* DictionaryComponent::Create(const Ticket& ticket) { if (!config->GetString(ticket.name_space + "/prism", &prism_name)) { prism_name = dict_name; } - return CreateDictionaryWithName(dict_name, prism_name); + vector packs; + if (auto pack_list = config->GetList(ticket.name_space + "/packs")) { + for (const auto& item : *pack_list) { + if (auto value = As(item)) { + packs.push_back(value->str()); + } + } + } + return Create(std::move(dict_name), + std::move(prism_name), + std::move(packs)); } -Dictionary* -DictionaryComponent::CreateDictionaryWithName(const string& dict_name, - const string& prism_name) { - // obtain prism and table objects - auto table = table_map_[dict_name].lock(); - if (!table) { +Dictionary* DictionaryComponent::Create(string dict_name, + string prism_name, + vector packs) { + // obtain prism and primary table objects + auto primary_table = table_map_[dict_name].lock(); + if (!primary_table) { auto file_path = table_resource_resolver_->ResolvePath(dict_name).string(); - table_map_[dict_name] = table = New
(file_path); + table_map_[dict_name] = primary_table = New
(file_path); } auto prism = prism_map_[prism_name].lock(); if (!prism) { auto file_path = prism_resource_resolver_->ResolvePath(prism_name).string(); prism_map_[prism_name] = prism = New(file_path); } - return new Dictionary(dict_name, table, prism); + vector> tables = {std::move(primary_table)}; + for (const auto& pack : packs) { + auto table = table_map_[pack].lock(); + if (!table) { + auto file_path = table_resource_resolver_->ResolvePath(pack).string(); + table_map_[pack] = table = New
(file_path); + } + tables.push_back(std::move(table)); + } + return new Dictionary(std::move(dict_name), + std::move(packs), + std::move(tables), + std::move(prism)); } } // namespace rime diff --git a/src/rime/dict/dictionary.h b/src/rime/dict/dictionary.h index b06acb551c..ee03d7c51e 100644 --- a/src/rime/dict/dictionary.h +++ b/src/rime/dict/dictionary.h @@ -79,8 +79,9 @@ struct Ticket; class Dictionary : public Class { public: - RIME_API Dictionary(const string& name, - an
table, + RIME_API Dictionary(string name, + vector packs, + vector> tables, an prism); virtual ~Dictionary(); @@ -103,12 +104,15 @@ class Dictionary : public Class { const string& name() const { return name_; } RIME_API bool loaded() const; - an
table() { return table_; } - an prism() { return prism_; } + const vector& packs() const { return packs_; } + const vector>& tables() const { return tables_; } + const an
& primary_table() const { return tables_[0]; } + const an& prism() const { return prism_; } private: string name_; - an
table_; + vector packs_; + vector> tables_; an prism_; }; @@ -119,8 +123,9 @@ class DictionaryComponent : public Dictionary::Component { DictionaryComponent(); ~DictionaryComponent() override; Dictionary* Create(const Ticket& ticket) override; - Dictionary* CreateDictionaryWithName(const string& dict_name, - const string& prism_name); + Dictionary* Create(string dict_name, + string prism_name, + vector packs); private: map> prism_map_; diff --git a/src/rime/dict/entry_collector.cc b/src/rime/dict/entry_collector.cc index 5a62802e20..ad5857b454 100644 --- a/src/rime/dict/entry_collector.cc +++ b/src/rime/dict/entry_collector.cc @@ -13,11 +13,13 @@ namespace rime { -EntryCollector::EntryCollector() { -} +EntryCollector::EntryCollector() {} -EntryCollector::~EntryCollector() { -} +EntryCollector::EntryCollector(Syllabary&& fixed_syllabary) + : syllabary(std::move(fixed_syllabary)), + build_syllabary(false) {} + +EntryCollector::~EntryCollector() {} void EntryCollector::Configure(DictSettings* settings) { if (settings->use_preset_vocabulary()) { @@ -181,10 +183,17 @@ void EntryCollector::CreateEntry(const string &word, e.weight = 0.0; } } - // learn new syllables + // learn new syllables, or check if syllables are in the fixed syllabary. for (const string& s : e.raw_code) { - if (syllabary.find(s) == syllabary.end()) - syllabary.insert(s); + if (syllabary.find(s) == syllabary.end()) { + if (build_syllabary) { + syllabary.insert(s); + } else { + LOG(ERROR) << "dropping entry '" << e.text + << "' with invalid syllable: " << s; + return; + } + } } // learn new word bool is_word = (e.raw_code.size() == 1); diff --git a/src/rime/dict/entry_collector.h b/src/rime/dict/entry_collector.h index 2f505d4b1d..c0ce2c7ea8 100644 --- a/src/rime/dict/entry_collector.h +++ b/src/rime/dict/entry_collector.h @@ -34,13 +34,15 @@ class DictSettings; class EntryCollector : public PhraseCollector { public: Syllabary syllabary; + bool build_syllabary = true; vector entries; size_t num_entries = 0; ReverseLookupTable stems; public: EntryCollector(); - ~EntryCollector(); + explicit EntryCollector(Syllabary&& fixed_syllabary); + virtual ~EntryCollector(); void Configure(DictSettings* settings); void Collect(const vector& dict_files); diff --git a/src/rime/gear/memory.cc b/src/rime/gear/memory.cc index bde9a47854..2b70c1a668 100644 --- a/src/rime/gear/memory.cc +++ b/src/rime/gear/memory.cc @@ -62,7 +62,7 @@ Memory::Memory(const Ticket& ticket) { if (user_dict_) { user_dict_->Load(); if (dict_) - user_dict_->Attach(dict_->table(), dict_->prism()); + user_dict_->Attach(dict_->primary_table(), dict_->prism()); } } diff --git a/test/dictionary_test.cc b/test/dictionary_test.cc index 9b25b0d682..efc64506a1 100644 --- a/test/dictionary_test.cc +++ b/test/dictionary_test.cc @@ -17,7 +17,8 @@ class RimeDictionaryTest : public ::testing::Test { if (!dict_) { dict_.reset(new rime::Dictionary( "dictionary_test", - rime::New("dictionary_test.table.bin"), + {}, + {rime::New("dictionary_test.table.bin")}, rime::New("dictionary_test.prism.bin"))); } if (!rebuilt_) {