1use crate::{Span, Token};
2
3use super::Expr;
4
5pub struct Not {
7 inner: Box<dyn Expr>,
8}
9
10impl Not {
11 pub fn new(inner: impl Expr + 'static) -> Self {
12 Self {
13 inner: Box::new(inner),
14 }
15 }
16}
17
18impl Expr for Not {
19 fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
20 self.inner
21 .run(cursor, tokens, source)
22 .is_none()
23 .then(|| Span::empty(cursor))
24 }
25}
26
27#[cfg(test)]
28mod tests {
29 use crate::{
30 Document,
31 expr::{AnchorStart, ExprExt, SequenceExpr},
32 linting::tests::SpanVecExt,
33 };
34
35 use super::Not;
36
37 #[test]
38 fn rejects_expression_at_start() {
39 let document = Document::new_plain_english_curated("Give the rise to power.");
40 let expression = SequenceExpr::with(Not::new(AnchorStart))
41 .then_any_capitalization_of("give")
42 .then_whitespace()
43 .then_any_capitalization_of("the")
44 .then_whitespace()
45 .then_any_capitalization_of("rise")
46 .then_whitespace()
47 .then_any_capitalization_of("to");
48
49 let matches = expression
50 .iter_matches_in_doc(&document)
51 .collect::<Vec<_>>();
52
53 assert!(matches.is_empty());
54 }
55
56 #[test]
57 fn matches_expression_after_start() {
58 let document = Document::new_plain_english_curated("They give the rise to power.");
59 let expression = SequenceExpr::with(Not::new(AnchorStart))
60 .then_any_capitalization_of("give")
61 .then_whitespace()
62 .then_any_capitalization_of("the")
63 .then_whitespace()
64 .then_any_capitalization_of("rise")
65 .then_whitespace()
66 .then_any_capitalization_of("to");
67
68 let matches = expression
69 .iter_matches_in_doc(&document)
70 .collect::<Vec<_>>();
71
72 assert_eq!(matches.to_strings(&document), ["give the rise to"]);
73 }
74}