Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions harper-core/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod first_match_of;
mod fixed_phrase;
mod longest_match_of;
mod mergeable_words;
mod not;
mod optional;
mod pronoun_be;
mod reflexive_pronoun;
Expand Down Expand Up @@ -50,6 +51,7 @@ pub use first_match_of::FirstMatchOf;
pub use fixed_phrase::FixedPhrase;
pub use longest_match_of::LongestMatchOf;
pub use mergeable_words::MergeableWords;
pub use not::Not;
pub use optional::Optional;
pub use pronoun_be::PronounBe;
pub use reflexive_pronoun::ReflexivePronoun;
Expand Down
74 changes: 74 additions & 0 deletions harper-core/src/expr/not.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::{Span, Token};

use super::Expr;

/// A zero-width assertion that matches when its inner expression does not.
pub struct Not {
inner: Box<dyn Expr>,
}

impl Not {
pub fn new(inner: impl Expr + 'static) -> Self {
Self {
inner: Box::new(inner),
}
}
}

impl Expr for Not {
fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
self.inner
.run(cursor, tokens, source)
.is_none()
.then(|| Span::empty(cursor))
}
}

#[cfg(test)]
mod tests {
use crate::{
Document,
expr::{AnchorStart, ExprExt, SequenceExpr},
linting::tests::SpanVecExt,
};

use super::Not;

#[test]
fn rejects_expression_at_start() {
let document = Document::new_plain_english_curated("Give the rise to power.");
let expression = SequenceExpr::with(Not::new(AnchorStart))
.then_any_capitalization_of("give")
.then_whitespace()
.then_any_capitalization_of("the")
.then_whitespace()
.then_any_capitalization_of("rise")
.then_whitespace()
.then_any_capitalization_of("to");

let matches = expression
.iter_matches_in_doc(&document)
.collect::<Vec<_>>();

assert!(matches.is_empty());
}

#[test]
fn matches_expression_after_start() {
let document = Document::new_plain_english_curated("They give the rise to power.");
let expression = SequenceExpr::with(Not::new(AnchorStart))
.then_any_capitalization_of("give")
.then_whitespace()
.then_any_capitalization_of("the")
.then_whitespace()
.then_any_capitalization_of("rise")
.then_whitespace()
.then_any_capitalization_of("to");

let matches = expression
.iter_matches_in_doc(&document)
.collect::<Vec<_>>();

assert_eq!(matches.to_strings(&document), ["give the rise to"]);
}
}
Loading