1use std::collections::HashMap;
2
3use crate::{
4 KeyBudget, KeyKind, LangMode, LanguageBackend, QueryBudget, QueryVariantKind, SearchConfig,
5};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct QueryVariant {
10 pub text: String,
12 pub kind: QueryVariantKind,
14 pub weight: i32,
16}
17
18impl QueryVariant {
19 pub fn original(text: impl Into<String>) -> Self {
21 Self {
22 text: text.into(),
23 kind: QueryVariantKind::Original,
24 weight: 500,
25 }
26 }
27
28 pub fn normalized(text: impl Into<String>) -> Self {
30 Self {
31 text: text.into(),
32 kind: QueryVariantKind::Normalized,
33 weight: 450,
34 }
35 }
36
37 pub fn kana(text: impl Into<String>) -> Self {
39 Self {
40 text: text.into(),
41 kind: QueryVariantKind::Kana,
42 weight: 350,
43 }
44 }
45
46 pub fn romaji_to_kana(text: impl Into<String>) -> Self {
48 Self {
49 text: text.into(),
50 kind: QueryVariantKind::RomajiToKana,
51 weight: 200,
52 }
53 }
54
55 pub fn pinyin(text: impl Into<String>) -> Self {
57 Self {
58 text: text.into(),
59 kind: QueryVariantKind::Pinyin,
60 weight: 250,
61 }
62 }
63
64 pub fn initials(text: impl Into<String>) -> Self {
66 Self {
67 text: text.into(),
68 kind: QueryVariantKind::Initials,
69 weight: 250,
70 }
71 }
72}
73
74#[derive(Clone, Debug, Default)]
75pub struct PlainBackend;
77
78impl LanguageBackend for PlainBackend {
79 fn mode(&self) -> LangMode {
80 LangMode::Plain
81 }
82
83 fn build_candidate_keys(&self, _text: &str, _budget: KeyBudget) -> Vec<crate::SearchKey> {
84 Vec::new()
85 }
86
87 fn expand_query(&self, query: &str, _budget: QueryBudget) -> Vec<QueryVariant> {
88 base_query_variants(query)
89 }
90}
91
92pub fn base_query_variants(query: &str) -> Vec<QueryVariant> {
94 let mut variants = vec![QueryVariant::original(query)];
95 let normalized = crate::normalize::normalize(query);
96 if normalized != query {
97 variants.push(QueryVariant::normalized(normalized));
98 }
99 variants
100}
101
102pub fn dedup_and_limit_variants(
104 variants: Vec<QueryVariant>,
105 max_query_variants: usize,
106) -> Vec<QueryVariant> {
107 let mut seen_coverage_by_text = HashMap::new();
108 let mut out = Vec::new();
109
110 for variant in variants {
111 let coverage = key_kind_coverage(variant.kind);
112 let seen_coverage = seen_coverage_by_text
113 .entry(variant.text.clone())
114 .or_insert(0u16);
115 if coverage & !*seen_coverage != 0 {
116 *seen_coverage |= coverage;
117 out.push(variant);
118 }
119 if out.len() >= max_query_variants {
120 break;
121 }
122 }
123
124 out
125}
126
127pub(crate) fn prepare_query_variants(
129 query: &str,
130 backend: &dyn LanguageBackend,
131 config: &SearchConfig,
132) -> Vec<QueryVariant> {
133 dedup_and_limit_variants(
134 backend.expand_query(query, config.query_budget()),
135 config.max_query_variants,
136 )
137}
138
139pub(crate) fn key_blocked_by_config(
154 key: &crate::SearchKey,
155 config: &SearchConfig,
156 scorer_folds_case: bool,
157) -> bool {
158 key.kind == KeyKind::Normalized
159 && (config.case_sensitive || !config.normalize || (key.case_fold_only && scorer_folds_case))
160}
161
162pub(crate) fn variant_blocked_by_config(kind: QueryVariantKind, config: &SearchConfig) -> bool {
164 kind == QueryVariantKind::Normalized && (config.case_sensitive || !config.normalize)
165}
166
167fn key_kind_coverage(kind: QueryVariantKind) -> u16 {
168 compatible_key_kinds(kind)
169 .iter()
170 .fold(0, |coverage, kind| coverage | key_kind_bit(*kind))
171}
172
173pub fn key_kind_allowed(variant: &QueryVariant, kind: KeyKind) -> bool {
175 compatible_key_kinds(variant.kind).contains(&kind)
176}
177
178const ORIGINAL_QUERY_KEYS: &[KeyKind] = &[
179 KeyKind::Original,
180 KeyKind::Normalized,
181 KeyKind::RomajiReading,
182 KeyKind::PinyinFull,
183 KeyKind::PinyinJoined,
184 KeyKind::KoreanRomanized,
185 KeyKind::KoreanInitials,
186 KeyKind::KoreanKeyboard,
187 KeyKind::LearnedAlias,
188];
189const KANA_QUERY_KEYS: &[KeyKind] = &[KeyKind::KanaReading];
190const PINYIN_QUERY_KEYS: &[KeyKind] = &[KeyKind::PinyinFull, KeyKind::PinyinJoined];
191const INITIAL_QUERY_KEYS: &[KeyKind] = &[
192 KeyKind::PinyinInitials,
193 KeyKind::KoreanInitials,
194 KeyKind::LearnedAlias,
195];
196
197fn compatible_key_kinds(kind: QueryVariantKind) -> &'static [KeyKind] {
198 match kind {
199 QueryVariantKind::Original | QueryVariantKind::Normalized => ORIGINAL_QUERY_KEYS,
200 QueryVariantKind::Kana | QueryVariantKind::RomajiToKana => KANA_QUERY_KEYS,
201 QueryVariantKind::Pinyin => PINYIN_QUERY_KEYS,
202 QueryVariantKind::Initials => INITIAL_QUERY_KEYS,
203 }
204}
205
206fn key_kind_bit(kind: KeyKind) -> u16 {
207 1 << (kind as u16)
208}
209
210#[cfg(test)]
211mod tests;