initiative_core/command/token/
sequence.rs1use crate::app::AppMeta;
2use crate::command::prelude::*;
3
4use std::iter;
5use std::pin::Pin;
6
7use async_stream::stream;
8use futures::prelude::*;
9
10pub fn match_input<'a, 'b>(
11 token: &'a Token,
12 input: &'a str,
13 app_meta: &'b AppMeta,
14) -> Pin<Box<dyn Stream<Item = FuzzyMatch<'a>> + 'b>>
15where
16 'a: 'b,
17{
18 let tokens = if let TokenType::Sequence(tokens) = &token.token_type {
19 tokens
20 } else {
21 unreachable!();
22 };
23
24 match_input_with_tokens(token, input, app_meta, tokens)
25}
26
27pub fn match_input_with_tokens<'a, 'b>(
28 token: &'a Token,
29 input: &'a str,
30 app_meta: &'b AppMeta,
31 tokens: &'a [Token],
32) -> Pin<Box<dyn Stream<Item = FuzzyMatch<'a>> + 'b>>
33where
34 'a: 'b,
35{
36 if tokens.is_empty() {
37 Box::pin(stream::iter([FuzzyMatch::Overflow(
39 token.into(),
40 input.into(),
41 )]))
42 } else {
43 Box::pin(stream! {
44 for await fuzzy_match in tokens[0].match_input(input, app_meta) {
46 match fuzzy_match {
47
48 FuzzyMatch::Partial(token_match, completion) => {
50 yield FuzzyMatch::Partial(
51 TokenMatch::new(token, vec![token_match]),
52 completion,
53 );
54 }
55
56 FuzzyMatch::Exact(token_match) if tokens.len() == 1 => {
59 yield FuzzyMatch::Exact(
60 TokenMatch::new(token, vec![token_match]),
61 );
62 }
63
64 FuzzyMatch::Exact(token_match) => {
67 yield FuzzyMatch::Partial(
68 TokenMatch::new(token, vec![token_match]),
69 None,
70 );
71 }
72
73 FuzzyMatch::Overflow(token_match, remainder) if tokens.len() == 1 => {
76 yield FuzzyMatch::Overflow(
77 TokenMatch::new(token, vec![token_match]),
78 remainder,
79 );
80 }
81
82 FuzzyMatch::Overflow(token_match, remainder) => {
85 for await next_fuzzy_match in match_input_with_tokens(token, remainder.as_str(), app_meta, &tokens[1..]) {
86 yield next_fuzzy_match.map(|next_match| {
87 TokenMatch::new(
88 token,
89 iter::once(token_match.clone())
90 .chain(
91 next_match
92 .match_meta
93 .into_sequence()
94 .unwrap()
95 .into_iter(),
96 )
97 .collect::<Vec<_>>(),
98 )
99 });
100 }
101 }
102 }
103 }
104 })
105 }
106}
107
108#[cfg(test)]
109mod test {
110 use super::*;
111
112 use crate::test_utils as test;
113
114 #[derive(Hash)]
115 enum Marker {
116 Token,
117 }
118
119 #[tokio::test]
120 async fn match_input_test_empty() {
121 let sequence_token = sequence_m(Marker::Token, []);
122 test::assert_eq_unordered!(
123 [FuzzyMatch::Overflow(
124 TokenMatch::from(&sequence_token),
125 "badger".into()
126 )],
127 sequence_token
128 .match_input("badger", &test::app_meta())
129 .collect::<Vec<_>>()
130 .await,
131 );
132 }
133
134 #[tokio::test]
135 async fn match_input_test_exact() {
136 let tokens = [keyword("badger"), keyword("mushroom"), keyword("snake")];
137 let sequence_token = sequence(tokens.clone());
138
139 test::assert_eq_unordered!(
140 [FuzzyMatch::Exact(TokenMatch::new(
141 &sequence_token,
142 vec![
143 TokenMatch::from(&tokens[0]),
144 TokenMatch::from(&tokens[1]),
145 TokenMatch::from(&tokens[2]),
146 ],
147 ))],
148 sequence_token
149 .match_input("badger mushroom snake", &test::app_meta())
150 .collect::<Vec<_>>()
151 .await,
152 );
153 }
154
155 #[tokio::test]
156 async fn match_input_test_incomplete() {
157 let tokens = [keyword("badger"), keyword("mushroom"), keyword("snake")];
158 let sequence_token = sequence(tokens.clone());
159
160 test::assert_eq_unordered!(
161 [FuzzyMatch::Partial(
162 TokenMatch::new(
163 &sequence_token,
164 vec![TokenMatch::from(&tokens[0]), TokenMatch::from(&tokens[1])],
165 ),
166 None,
167 )],
168 sequence_token
169 .match_input("badger mushroom", &test::app_meta())
170 .collect::<Vec<_>>()
171 .await,
172 );
173 }
174
175 #[tokio::test]
176 async fn match_input_test_partial() {
177 let tokens = [keyword("badger"), keyword("mushroom"), keyword("snake")];
178 let sequence_token = sequence(tokens.clone());
179
180 test::assert_eq_unordered!(
181 [FuzzyMatch::Partial(
182 TokenMatch::new(
183 &sequence_token,
184 vec![
185 TokenMatch::from(&tokens[0]),
186 TokenMatch::from(&tokens[1]),
187 TokenMatch::from(&tokens[2]),
188 ],
189 ),
190 Some("ake".to_string()),
191 )],
192 sequence_token
193 .match_input("badger mushroom sn", &test::app_meta())
194 .collect::<Vec<_>>()
195 .await,
196 );
197 }
198
199 #[tokio::test]
200 async fn match_input_test_overflowing() {
201 let tokens = [keyword("badger"), keyword("mushroom"), keyword("snake")];
202 let sequence_token = sequence_m(Marker::Token, tokens.clone());
203
204 test::assert_eq_unordered!(
205 [FuzzyMatch::Overflow(
206 TokenMatch::new(
207 &sequence_token,
208 vec![
209 TokenMatch::from(&tokens[0]),
210 TokenMatch::from(&tokens[1]),
211 TokenMatch::from(&tokens[2]),
212 ],
213 ),
214 " hippopotumus".into(),
215 )],
216 sequence_token
217 .match_input("badger mushroom snake hippopotumus", &test::app_meta())
218 .collect::<Vec<_>>()
219 .await,
220 );
221 }
222
223 #[tokio::test]
224 async fn match_input_test_with_any_phrase() {
225 let tokens = [keyword("badger"), any_phrase(), any_word()];
226 let [keyword_token, any_phrase_token, any_word_token] = tokens.clone();
227 let sequence_token = sequence(tokens);
228
229 test::assert_eq_unordered!(
230 [
231 FuzzyMatch::Overflow(
232 TokenMatch::new(
233 &sequence_token,
234 vec![
235 TokenMatch::from(&keyword_token),
236 TokenMatch::new(&any_phrase_token, "badger"),
237 TokenMatch::new(&any_word_token, "badger"),
238 ],
239 ),
240 " mushroom".into(),
241 ),
242 FuzzyMatch::Exact(TokenMatch::new(
243 &sequence_token,
244 vec![
245 TokenMatch::from(&keyword_token),
246 TokenMatch::new(&any_phrase_token, "badger badger"),
247 TokenMatch::new(&any_word_token, "mushroom"),
248 ],
249 )),
250 FuzzyMatch::Partial(
251 TokenMatch::new(
252 &sequence_token,
253 vec![
254 TokenMatch::from(&keyword_token),
255 TokenMatch::new(&any_phrase_token, "badger badger mushroom"),
256 ],
257 ),
258 None,
259 ),
260 ],
261 sequence_token
262 .match_input("badger badger badger mushroom", &test::app_meta())
263 .collect::<Vec<_>>()
264 .await,
265 );
266 }
267}