aede_core/query/
parsing.rs1use super::*;
4
5fn tokenize(input: &str) -> Result<Vec<String>, QueryError> {
14 let mut out: Vec<String> = Vec::new();
15 let mut current = String::new();
16 let mut quote: Option<char> = None;
17 for ch in input.chars() {
18 match quote {
19 Some(q) if ch == q => quote = None,
20 Some(_) => current.push(ch),
21 None if ch == '"' || ch == '\'' => quote = Some(ch),
22 None if ch.is_whitespace() => {
23 if !current.is_empty() {
24 out.push(std::mem::take(&mut current));
25 }
26 }
27 None if ch == '(' || ch == ')' => {
28 if !current.is_empty() {
29 out.push(std::mem::take(&mut current));
30 }
31 out.push(ch.to_string());
32 }
33 None => current.push(ch),
34 }
35 }
36 if quote.is_some() {
37 return Err(error("a quotation mark is left open"));
38 }
39 if !current.is_empty() {
40 out.push(current);
41 }
42 Ok(out)
43}
44
45pub fn parse(input: &str) -> Result<Query, QueryError> {
57 if let Some(value) = input.strip_prefix("browse-v1:") {
58 return browse::parse(value).map(|filter| Query::Browse(Box::new(filter)));
59 }
60 let words = tokenize(input)?;
61 if words.is_empty() {
62 return Ok(Query::All);
63 }
64 let mut at = 0usize;
65 let query = parse_or(&words, &mut at, 0)?;
66 if at < words.len() {
67 return Err(error(format!("\"{}\" is one bracket too many", words[at])));
68 }
69 Ok(query)
70}
71
72fn parse_or(words: &[String], at: &mut usize, depth: usize) -> Result<Query, QueryError> {
73 let mut parts = vec![parse_and(words, at, depth)?];
74 while *at < words.len() && is_or(&words[*at]) {
75 *at += 1;
76 parts.push(parse_and(words, at, depth)?);
77 }
78 Ok(if parts.len() == 1 {
79 parts.remove(0)
80 } else {
81 Query::Or(parts)
82 })
83}
84
85fn is_or(word: &str) -> bool {
86 word.eq_ignore_ascii_case("or") || word == "|" || word == "||"
87}
88
89fn parse_and(words: &[String], at: &mut usize, depth: usize) -> Result<Query, QueryError> {
90 let mut parts: Vec<Query> = Vec::new();
91 while *at < words.len() && words[*at] != ")" && !is_or(&words[*at]) {
92 if words[*at].eq_ignore_ascii_case("and") {
94 if parts.is_empty() {
95 return Err(error("AND needs an expression before it"));
96 }
97 *at += 1;
98 if *at >= words.len()
99 || words[*at] == ")"
100 || is_or(&words[*at])
101 || words[*at].eq_ignore_ascii_case("and")
102 {
103 return Err(error("AND needs an expression after it"));
104 }
105 parts.push(parse_unary(words, at, depth)?);
106 continue;
107 }
108 parts.push(parse_unary(words, at, depth)?);
109 }
110 if parts.is_empty() {
111 return Err(error("something is missing between the brackets"));
112 }
113 Ok(if parts.len() == 1 {
114 parts.remove(0)
115 } else {
116 Query::And(parts)
117 })
118}
119
120fn parse_unary(words: &[String], at: &mut usize, depth: usize) -> Result<Query, QueryError> {
121 let Some(word) = words.get(*at) else {
122 return Err(error("an expression is missing"));
123 };
124 parse_unary_word(words, at, depth, word)
125}
126
127fn parse_unary_word(
128 words: &[String],
129 at: &mut usize,
130 depth: usize,
131 word: &str,
132) -> Result<Query, QueryError> {
133 if depth > 128 {
134 return Err(error("a query exceeds 128 nested brackets or negations"));
135 }
136 if word.eq_ignore_ascii_case("and") || is_or(word) {
137 return Err(error(format!("{word} needs an expression on both sides")));
138 }
139 if word == "-" || word.eq_ignore_ascii_case("not") {
140 *at += 1;
141 if *at >= words.len() {
142 return Err(error("nothing follows the minus sign"));
143 }
144 return Ok(Query::Not(Box::new(parse_unary(words, at, depth + 1)?)));
145 }
146 if let Some(rest) = word.strip_prefix('-')
147 && !rest.is_empty()
148 {
149 let negated = parse_unary_word(words, at, depth + 1, rest)?;
153 return Ok(Query::Not(Box::new(negated)));
154 }
155 if word == "(" {
156 *at += 1;
157 let inside = parse_or(words, at, depth + 1)?;
158 if *at >= words.len() || words[*at] != ")" {
159 return Err(error("a bracket is left open"));
160 }
161 *at += 1;
162 return Ok(inside);
163 }
164 if word == ")" {
165 return Err(error("a closing bracket has nothing to close"));
166 }
167 let term = parse_term(word)?;
168 *at += 1;
169 Ok(Query::Term(term))
170}
171
172fn parse_term(word: &str) -> Result<Term, QueryError> {
173 let Some((name, value)) = word.split_once(':') else {
174 if let Some(field) = field_named(word)
177 && asks_whether_it_holds_anything(&field)
178 {
179 return Ok(Term {
180 field,
181 test: Test::Set,
182 });
183 }
184 return Ok(Term {
185 field: Field::Anything,
186 test: Test::Contains(word.to_string()),
187 });
188 };
189 let Some(field) = field_named(name) else {
190 return Err(error(format!(
191 "\"{name}\" is not a field.\nFields: {}",
192 FIELD_NAMES
193 .iter()
194 .map(|(n, _)| *n)
195 .collect::<Vec<_>>()
196 .join(", ")
197 )));
198 };
199 let test = parse_test(&field, value)?;
200 Ok(Term { field, test })
201}
202
203fn parse_test(field: &Field, value: &str) -> Result<Test, QueryError> {
204 if value.is_empty() {
205 return Err(error("a field needs something after the colon"));
206 }
207 if is_flag(field) {
210 match value.to_lowercase().as_str() {
211 "true" | "yes" | "1" => return Ok(Test::Set),
212 "false" | "no" | "0" => return Ok(Test::Unset),
213 other => {
214 return Err(error(format!(
215 "\"{other}\" is not a yes or a no: try true or false"
216 )));
217 }
218 }
219 }
220 if is_numeric(field) {
221 if let Some((low, high)) = value.split_once("..") {
223 let low = parse_number(field, low)?;
224 let high = parse_number(field, high)?;
225 if low.is_none() && high.is_none() {
226 return Err(error("a range needs at least one bound"));
227 }
228 if let (Some(low), Some(high)) = (low, high)
229 && low > high
230 {
231 return Err(error("the lower range bound exceeds the upper bound"));
232 }
233 return Ok(Test::Between(low, high));
234 }
235 for (prefix, compare) in [
236 (">=", Compare::AtLeast),
237 ("<=", Compare::AtMost),
238 (">", Compare::Greater),
239 ("<", Compare::Less),
240 ("=", Compare::Equal),
241 ] {
242 if let Some(rest) = value.strip_prefix(prefix) {
243 let Some(number) = parse_number(field, rest)? else {
244 return Err(error(format!("\"{rest}\" is not a number")));
245 };
246 return Ok(Test::Compare(compare, number));
247 }
248 }
249 let Some(number) = parse_number(field, value)? else {
250 return Err(error(format!("\"{value}\" is not a number")));
251 };
252 return Ok(Test::Compare(Compare::Equal, number));
253 }
254 if let Some(exact) = value.strip_prefix('=') {
255 return Ok(Test::Is(exact.to_string()));
256 }
257 Ok(Test::Contains(value.to_string()))
258}
259
260fn parse_number(field: &Field, raw: &str) -> Result<Option<f64>, QueryError> {
262 let raw = raw.trim();
263 if raw.is_empty() {
264 return Ok(None);
265 }
266 if *field == Field::Duration
267 && let Some((minutes, seconds)) = raw.split_once(':')
268 {
269 let minutes: f64 = minutes
270 .parse()
271 .map_err(|_| error(format!("\"{raw}\" is not a length")))?;
272 let seconds: f64 = seconds
273 .parse()
274 .map_err(|_| error(format!("\"{raw}\" is not a length")))?;
275 if !minutes.is_finite()
276 || minutes < 0.0
277 || minutes.fract() != 0.0
278 || !seconds.is_finite()
279 || !(0.0..60.0).contains(&seconds)
280 {
281 return Err(error(format!(
282 "\"{raw}\" needs whole minutes and seconds below 60"
283 )));
284 }
285 return finite_number((minutes * 60.0 + seconds) * 1000.0, raw).map(Some);
286 }
287 let number: f64 = raw
288 .parse()
289 .map_err(|_| error(format!("\"{raw}\" is not a number")))?;
290 let number = if *field == Field::Duration {
292 number * 1000.0
293 } else {
294 number
295 };
296 finite_number(number, raw).map(Some)
297}
298
299fn finite_number(number: f64, raw: &str) -> Result<f64, QueryError> {
300 if !number.is_finite() || number < 0.0 {
301 return Err(error(format!("\"{raw}\" must be finite and nonnegative")));
302 }
303 Ok(number)
304}