scah_query_ir/query/compiler/
builder.rs1use super::SelectorParseError;
2use super::query::{Query, QuerySection, QuerySectionId, TransitionId};
3use super::transition::Transition;
4
5#[derive(PartialEq, Debug, Default, Clone, Copy)]
31pub struct Save {
32 pub inner_html: bool,
35 pub text_content: bool,
38}
39
40impl Save {
41 pub fn only_inner_html() -> Self {
43 Self {
44 inner_html: true,
45 text_content: false,
46 }
47 }
48
49 pub fn only_text_content() -> Self {
51 Self {
52 inner_html: false,
53 text_content: true,
54 }
55 }
56
57 pub fn all() -> Self {
59 Self {
60 inner_html: true,
61 text_content: true,
62 }
63 }
64
65 pub fn none() -> Self {
70 Self {
71 inner_html: false,
72 text_content: false,
73 }
74 }
75}
76
77#[derive(PartialEq, Debug, Clone, Copy)]
84pub enum SelectionKind {
85 All,
87 First,
89}
90
91#[derive(Debug, Clone)]
114pub struct QueryBuilder<'query> {
115 pub states: Vec<Transition<'query>>,
117 pub selection: Vec<QuerySection<'query>>,
119}
120
121impl<'query> QueryBuilder<'query> {
122 pub fn all(mut self, query: &'query str, save: Save) -> Result<Self, SelectorParseError> {
123 assert!(!self.selection.is_empty());
124
125 let current_state_len = self.states.len();
126 let mut states = Transition::generate_transitions_from_string(query)?;
127
128 let parent_index = QuerySectionId(self.selection.len() - 1);
129 let range = TransitionId(current_state_len)..TransitionId(current_state_len + states.len());
130 self.selection.push(QuerySection::new(
131 query,
132 save,
133 SelectionKind::All,
134 range,
135 Some(parent_index),
136 ));
137
138 self.states.append(&mut states);
139
140 Ok(self)
141 }
142
143 pub fn first(mut self, query: &'query str, save: Save) -> Result<Self, SelectorParseError> {
149 assert!(!self.selection.is_empty());
150
151 let current_state_len = self.states.len();
152 let mut states = Transition::generate_transitions_from_string(query)?;
153
154 let parent_index = QuerySectionId(self.selection.len() - 1);
155 let range = TransitionId(current_state_len)..TransitionId(current_state_len + states.len());
156 self.selection.push(QuerySection::new(
157 query,
158 save,
159 SelectionKind::First,
160 range,
161 Some(parent_index),
162 ));
163
164 self.states.append(&mut states);
165
166 Ok(self)
167 }
168
169 pub fn append(&mut self, parent: QuerySectionId, mut other: Self) {
174 let state_length = self.states.len();
175 let selection_length = self.selection.len();
176
177 let mut last_sibling: Option<QuerySectionId> = {
178 if parent.index() + 1 == self.selection.len() {
179 None
180 } else {
181 let mut sibling_index = QuerySectionId(parent.index() + 1);
182 while self.selection[sibling_index.index()].next_sibling.is_some() {
183 sibling_index = self.selection[sibling_index.index()].next_sibling.unwrap();
184 }
185
186 Some(sibling_index)
187 }
188 };
189 for index in 0..other.selection.len() {
190 let query = &mut other.selection[index];
191 query.range.start = TransitionId(query.range.start.index() + state_length);
192 query.range.end = TransitionId(query.range.end.index() + state_length);
193 if let Some(next_sibling) = query.next_sibling {
194 query.next_sibling = Some(QuerySectionId(next_sibling.index() + selection_length));
195 }
196
197 if let Some(idx) = query.parent {
198 query.parent = Some(QuerySectionId(idx.index() + selection_length));
199 } else {
200 query.parent = Some(parent);
201
202 let current_index = QuerySectionId(selection_length + index);
203 last_sibling = match last_sibling {
204 Some(sibling) => {
205 if sibling.index() < selection_length {
206 self.selection[sibling.index()].next_sibling = Some(current_index);
207 } else {
208 other.selection[sibling.index() - selection_length].next_sibling =
209 Some(current_index);
210 }
211 Some(current_index)
212 }
213 None => Some(current_index),
214 };
215 }
216 }
217 self.states.append(&mut other.states);
218 self.selection.append(&mut other.selection);
219 }
220
221 pub fn then<F, I>(mut self, func: F) -> Result<Self, SelectorParseError>
244 where
245 F: FnOnce(QueryFactory) -> Result<I, SelectorParseError>,
246 I: IntoIterator<Item = Self>,
247 {
248 let factory = QueryFactory {};
249 let children = func(factory)?;
250
251 let current_index = QuerySectionId(self.selection.len() - 1);
252 for child in children {
253 self.append(current_index, child);
254 }
255 Ok(self)
256 }
257
258 fn exit_at_section(&self) -> Option<QuerySectionId> {
259 fn search_for_single_exit_section(
266 index: QuerySectionId,
267 list: &[QuerySection<'_>],
268 ) -> Option<QuerySectionId> {
269 if index.index() >= list.len() {
272 return None;
273 }
274 let section = &list[index.index()];
275 let stop_here = match §ion.kind {
276 SelectionKind::All => return None,
278
279 SelectionKind::First => section.save != Save::none(),
281 };
282 if stop_here {
283 return Some(index);
284 }
285
286 let mut child = QuerySectionId(index.index() + 1);
287 if child.index() >= list.len() {
288 return Some(index);
289 }
290
291 let mut child_response: Option<QuerySectionId> = None;
292 if let Some(parent) = list[child.index()].parent
293 && parent == index
294 {
295 loop {
296 child_response = match child_response {
297 None => search_for_single_exit_section(child, list),
298 Some(_) => {
299 return Some(index);
302 }
303 };
304
305 if let Some(sibling) = list[child.index()].next_sibling {
306 child = sibling;
307 } else {
308 break;
309 }
310 }
311 }
312
313 if child_response.is_some() {
314 return child_response;
315 }
316 Some(index)
317 }
318
319 search_for_single_exit_section(QuerySectionId(0), &self.selection)
320 }
321}
322
323impl<'query> QueryBuilder<'query> {
324 pub fn build(self) -> Query<'query> {
330 let exit_at_section_end = self.exit_at_section();
331 let states_box = self.states.into_boxed_slice();
332 let query_box = self.selection.into_boxed_slice();
333 Query {
334 states: states_box,
335 queries: query_box,
336 exit_at_section_end,
337 }
338 }
339}
340
341pub struct QueryFactory {}
347impl<'query> QueryFactory {
348 pub fn all(
350 &self,
351 query: &'query str,
352 save: Save,
353 ) -> Result<QueryBuilder<'query>, SelectorParseError> {
354 Query::all(query, save)
355 }
356
357 pub fn first(
359 &self,
360 query: &'query str,
361 save: Save,
362 ) -> Result<QueryBuilder<'query>, SelectorParseError> {
363 Query::first(query, save)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use crate::{ClassSelections, Query, QuerySectionId, Save, SelectionKind};
370
371 #[test]
372 fn test_builder_with_class_chaining() {
373 let query = Query::all("a.blue.exit", Save::all()).unwrap().build();
374 assert_eq!(
375 query.states[0].predicate.classes,
376 ClassSelections::from_static(&["blue", "exit"])
377 );
378 }
379
380 #[test]
381 fn test_early_exit() {
382 let query = Query::all("a", Save::all()).unwrap();
383 assert_eq!(query.exit_at_section(), None);
384
385 let query = Query::all("a", Save::none()).unwrap();
386 assert_eq!(query.exit_at_section(), None);
387
388 let query = Query::first("a", Save::all()).unwrap();
389 assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
390
391 let query = Query::first("a", Save::none()).unwrap();
392 assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
393
394 let query = Query::all("p", Save::all())
395 .unwrap()
396 .first("a", Save::all())
397 .unwrap();
398 assert_eq!(query.exit_at_section(), None);
399
400 let query = Query::first("p", Save::all())
401 .unwrap()
402 .all("a", Save::all())
403 .unwrap();
404 assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
405
406 let query = Query::first("p", Save::all())
407 .unwrap()
408 .first("a", Save::all())
409 .unwrap();
410 assert_eq!(query.exit_at_section(), Some(QuerySectionId(0)));
411
412 let query = Query::first("p", Save::none())
413 .unwrap()
414 .first("a", Save::none())
415 .unwrap();
416 assert_eq!(query.exit_at_section(), Some(QuerySectionId(1)));
417 }
418
419 #[test]
420 fn test_invalid_selectors_fail_to_build() {
421 let invalid = [
422 "",
423 "a > ",
424 ".",
425 "#",
426 " a ~ b",
427 "a + b",
428 "a[]",
429 "*",
430 "a[123=\"321\"]",
431 ];
432
433 for selector in invalid {
434 assert!(Query::all(selector, Save::none()).is_err(), "{selector}");
435 }
436 }
437
438 #[test]
439 fn test_then_with_all_and_first() {
440 let query = Query::all("article", Save::none())
441 .unwrap()
442 .then(|article| {
443 Ok([
444 article.all("a[href]", Save::all())?,
445 article.first("h1", Save::only_text_content())?,
446 ])
447 })
448 .unwrap()
449 .build();
450
451 assert_eq!(query.queries.len(), 3);
452 assert_eq!(query.queries[1].source, "a[href]");
453 assert_eq!(query.queries[2].source, "h1");
454 }
455
456 #[test]
457 fn test_then_propagates_invalid_selector_from_callback() {
458 let error = Query::all("article", Save::none())
459 .unwrap()
460 .then(|article| {
461 Ok([
462 article.all("a[href]", Save::all())?,
463 article.first("a + b", Save::only_text_content())?,
464 ])
465 })
466 .unwrap_err();
467
468 assert_eq!(error.message(), "unsupported combinator '+'");
469 }
470
471 #[test]
472 fn test_then_builds_sibling_links_in_callback_order() {
473 let query = Query::all("article", Save::none())
474 .unwrap()
475 .then(|article| {
476 Ok([
477 article.all("a[href]", Save::all())?,
478 article.first("h1", Save::only_text_content())?,
479 article.all("p", Save::none())?,
480 ])
481 })
482 .unwrap()
483 .build();
484
485 assert_eq!(query.queries.len(), 4);
486 assert_eq!(query.queries[1].parent, Some(QuerySectionId(0)));
487 assert_eq!(query.queries[2].parent, Some(QuerySectionId(0)));
488 assert_eq!(query.queries[3].parent, Some(QuerySectionId(0)));
489 assert_eq!(query.queries[1].next_sibling, Some(QuerySectionId(2)));
490 assert_eq!(query.queries[2].next_sibling, Some(QuerySectionId(3)));
491 assert_eq!(query.queries[3].next_sibling, None);
492 assert_eq!(query.queries[1].kind, SelectionKind::All);
493 assert_eq!(query.queries[2].kind, SelectionKind::First);
494 assert_eq!(query.queries[3].kind, SelectionKind::All);
495 }
496
497 #[test]
498 fn test_nested_then_supports_all_and_first() {
499 let query = Query::all("article", Save::none())
500 .unwrap()
501 .then(|article| {
502 Ok([article.all("section", Save::none())?.then(|section| {
503 Ok([
504 section.first("h2", Save::only_text_content())?,
505 section.all("a[href]", Save::all())?,
506 ])
507 })?])
508 })
509 .unwrap()
510 .build();
511
512 assert_eq!(query.queries.len(), 4);
513 assert_eq!(query.queries[1].source, "section");
514 assert_eq!(query.queries[2].source, "h2");
515 assert_eq!(query.queries[3].source, "a[href]");
516 assert_eq!(query.queries[2].parent, Some(QuerySectionId(1)));
517 assert_eq!(query.queries[3].parent, Some(QuerySectionId(1)));
518 assert_eq!(query.queries[2].next_sibling, Some(QuerySectionId(3)));
519 }
520
521 #[test]
522 fn test_then_returns_error_without_partial_append() {
523 let builder = Query::all("article", Save::none())
524 .unwrap()
525 .then(|article| {
526 let first = article.all("section", Save::none())?;
527 let second = article.first("a + b", Save::all());
528 match second {
529 Ok(second) => Ok([first, second]),
530 Err(err) => Err(err),
531 }
532 });
533
534 assert!(builder.is_err());
535 let error = builder.unwrap_err();
536 assert_eq!(error.message(), "unsupported combinator '+'");
537 }
538}