Skip to main content

harper_core/expr/
reflexive_pronoun.rs

1use crate::{
2    Span, Token,
3    expr::{Expr, FirstMatchOf},
4    patterns::WordSet,
5};
6
7// These are considered ungrammatical, or are at least not in `dictionary.dict` but are commonly used anyway.
8// The tests below check if this changes so we can update this `Expr`
9const BAD_REFLEXIVE_PRONOUNS: &[&str] = &["hisself", "oneselves", "theirself", "theirselves"];
10
11/// Matches reflexive pronouns with configurable strictness.
12///
13/// By default, only matches standard English reflexive pronouns. Use `with_common_errors()` to include
14/// frequently encountered non-standard forms like "hisself" or "theirself".
15pub struct ReflexivePronoun {
16    include_common_errors: bool,
17}
18
19impl Default for ReflexivePronoun {
20    fn default() -> Self {
21        Self::standard()
22    }
23}
24
25impl ReflexivePronoun {
26    /// Creates a matcher for standard English reflexive pronouns.
27    ///
28    /// Matches only the correct forms: "myself", "yourself", "himself", "herself", "itself",
29    /// "ourselves", "yourselves", and "themselves".
30    pub fn standard() -> Self {
31        Self {
32            include_common_errors: false,
33        }
34    }
35
36    /// Creates a matcher that includes non-standard but commonly used reflexive pronouns.
37    ///
38    /// In addition to standard forms, matches common errors like "hisself", "theirself",
39    /// and other non-standard forms that are frequently seen in user-generated content.
40    pub fn with_common_errors() -> Self {
41        Self {
42            include_common_errors: true,
43        }
44    }
45}
46
47impl Expr for ReflexivePronoun {
48    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
49        let good_pronouns = |token: &Token, _: &[char]| token.kind.is_reflexive_pronoun();
50        let mut expr = FirstMatchOf::new(vec![Box::new(good_pronouns)]);
51        if self.include_common_errors {
52            expr.add(WordSet::new(BAD_REFLEXIVE_PRONOUNS));
53        }
54        expr.run(cursor, tokens, source)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use crate::{
61        Document, TokenKind,
62        expr::{ExprExt, ReflexivePronoun, reflexive_pronoun::BAD_REFLEXIVE_PRONOUNS},
63    };
64
65    // These are considered grammatically correct, or are at least in `dictionary.dict`.
66    // The tests below check if this changes so we can update this `Expr`
67    const GOOD_REFLEXIVE_PRONOUNS: &[&str] = &[
68        "herself",
69        "himself",
70        "itself",
71        "myself",
72        "oneself",
73        "ourself",
74        "ourselves",
75        "themselves",
76        "thyself",
77        "yourself",
78        "yourselves",
79    ];
80
81    fn test_pronoun(word: &str) {
82        let doc = Document::new_plain_english_curated(word);
83        let token = doc.tokens().next().expect("No tokens in document");
84
85        let is_good_pron = GOOD_REFLEXIVE_PRONOUNS.contains(&word);
86        let is_bad_pron = BAD_REFLEXIVE_PRONOUNS.contains(&word);
87
88        match (is_good_pron, is_bad_pron, &token.kind) {
89            (true, false, TokenKind::Word(Some(md))) => {
90                assert!(md.is_pronoun());
91                assert!(md.is_reflexive_pronoun());
92            }
93            (true, false, TokenKind::Word(None)) => {
94                panic!("Widely accepted pronoun '{word}' has gone missing from the dictionary!")
95            }
96            (false, true, TokenKind::Word(Some(_))) => panic!(
97                "Unaccepted pronoun '{word}' that's used in bad English is now in the dictionary!"
98            ),
99            (false, true, TokenKind::Word(None)) => {}
100            (false, false, TokenKind::Word(Some(_))) => panic!(
101                "non-pronoun '{word}' is made up just for testing but is now in the dictionary!"
102            ),
103            (false, false, TokenKind::Word(None)) => {}
104            (true, true, _) => panic!("'{word}' is in both good and bad lists"),
105            _ => panic!("'{word}' doesn't match any expected case"),
106        }
107    }
108
109    #[test]
110    fn test_good_reflexive_pronouns() {
111        for word in GOOD_REFLEXIVE_PRONOUNS {
112            test_pronoun(word);
113        }
114    }
115
116    #[test]
117    fn test_bad_reflexive_pronouns() {
118        for word in BAD_REFLEXIVE_PRONOUNS {
119            test_pronoun(word);
120        }
121    }
122
123    // It's expected that nobody uses these words even in bad English.
124    #[test]
125    fn test_non_pronouns() {
126        test_pronoun("myselves");
127        test_pronoun("weselves");
128        test_pronoun("usself");
129        test_pronoun("usselves");
130    }
131
132    #[test]
133    fn ensure_standard_ctor_includes_myself() {
134        let doc =
135            Document::new_plain_english_curated("If you want something done, do it yourself.");
136        let rp = ReflexivePronoun::standard();
137        let matches = rp.iter_matches_in_doc(&doc);
138        assert_eq!(matches.count(), 1);
139    }
140
141    #[test]
142    fn ensure_default_ctor_includes_myself() {
143        let doc = Document::new_plain_english_curated(
144            "I wanted a reflexive pronoun module, so I wrote one myself.",
145        );
146        let rp = ReflexivePronoun::default();
147        let matches = rp.iter_matches_in_doc(&doc);
148        assert_eq!(matches.count(), 1);
149    }
150
151    #[test]
152    fn ensure_with_common_errors_includes_hisself() {
153        let doc = Document::new_plain_english_curated("He teached hisself English.");
154        let rp = ReflexivePronoun::with_common_errors();
155        let matches = rp.iter_matches_in_doc(&doc);
156        assert_eq!(matches.count(), 1);
157    }
158
159    #[test]
160    fn ensure_standard_ctor_excludes_hisself() {
161        let doc = Document::new_plain_english_curated("Was he pleased with hisself?");
162        let rp = ReflexivePronoun::standard();
163        let matches = rp.iter_matches_in_doc(&doc);
164        assert_eq!(matches.count(), 0);
165    }
166
167    #[test]
168    fn ensure_default_ctor_excludes_theirself() {
169        let doc = Document::new_plain_english_curated("They look at theirself in the mirror.");
170        let rp = ReflexivePronoun::default();
171        let matches = rp.iter_matches_in_doc(&doc);
172        assert_eq!(matches.count(), 0);
173    }
174}