harper_core/
word_metadata.rs

1use harper_brill::UPOS;
2use is_macro::Is;
3use itertools::Itertools;
4use paste::paste;
5use serde::{Deserialize, Serialize};
6use strum::{EnumCount, VariantArray};
7use strum_macros::{Display, EnumCount, EnumString, VariantArray};
8
9use std::convert::TryFrom;
10
11use crate::spell::WordId;
12use crate::{Document, TokenKind, TokenStringExt};
13
14#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Hash)]
15pub struct WordMetadata {
16    pub noun: Option<NounData>,
17    pub pronoun: Option<PronounData>,
18    pub verb: Option<VerbData>,
19    pub adjective: Option<AdjectiveData>,
20    pub adverb: Option<AdverbData>,
21    pub conjunction: Option<ConjunctionData>,
22    pub swear: Option<bool>,
23    /// The dialects this word belongs to.
24    /// If no dialects are defined, it can be assumed that the word is
25    /// valid in all dialects of English.
26    #[serde(default = "default_default")]
27    pub dialects: DialectFlags,
28    /// Whether the word is a [determiner](https://en.wikipedia.org/wiki/English_determiners).
29    pub determiner: Option<DeterminerData>,
30    /// Whether the word is a [preposition](https://www.merriam-webster.com/dictionary/preposition).
31    #[serde(default = "default_false")]
32    pub preposition: bool,
33    /// Whether the word is considered especially common.
34    #[serde(default = "default_false")]
35    pub common: bool,
36    #[serde(default = "default_none")]
37    pub derived_from: Option<WordId>,
38    /// Generated by a chunker
39    pub np_member: Option<bool>,
40    /// Generated by a POS tagger
41    pub pos_tag: Option<UPOS>,
42}
43
44/// Needed for `serde`
45fn default_false() -> bool {
46    false
47}
48
49/// Needed for `serde`
50fn default_none<T>() -> Option<T> {
51    None
52}
53
54/// Needed for `serde`
55fn default_default<T: Default>() -> T {
56    T::default()
57}
58
59macro_rules! generate_metadata_queries {
60    ($($category:ident has $($sub:ident),*).*) => {
61        paste! {
62            pub fn is_likely_homograph(&self) -> bool {
63                [self.is_determiner(), self.preposition, $(
64                    self.[< is_ $category >](),
65                )*].iter().map(|b| *b as u8).sum::<u8>() > 1
66            }
67
68            $(
69                #[doc = concat!("Checks if the word is definitely a ", stringify!($category), ".")]
70                pub fn [< is_ $category >](&self) -> bool {
71                    self.$category.is_some()
72                }
73
74                $(
75                    #[doc = concat!("Checks if the word is definitely a ", stringify!($category), " and more specifically is labeled as (a) ", stringify!($sub), ".")]
76                    pub fn [< is_ $sub _ $category >](&self) -> bool {
77                        matches!(
78                            self.$category,
79                            Some([< $category:camel Data >]{
80                                [< is_ $sub >]: Some(true),
81                                ..
82                            })
83                        ) }
84
85                    #[doc = concat!("Checks if the word is definitely a ", stringify!($category), " and more specifically is labeled as __not__ (a) ", stringify!($sub), ".")]
86                    pub fn [< is_non_ $sub _ $category >](&self) -> bool {
87                        matches!(
88                            self.$category,
89                            Some([< $category:camel Data >]{
90                                [< is_ $sub >]: None | Some(false),
91                                ..
92                            })
93                        )
94                    }
95                )*
96            )*
97        }
98    };
99}
100
101impl WordMetadata {
102    /// Produce a copy of `self` with the known properties of `other` set.
103    pub fn or(&self, other: &Self) -> Self {
104        macro_rules! merge {
105            ($a:expr, $b:expr) => {
106                match ($a, $b) {
107                    (Some(a), Some(b)) => Some(a.or(&b)),
108                    (Some(a), None) => Some(a),
109                    (None, Some(b)) => Some(b),
110                    (None, None) => None,
111                }
112            };
113        }
114
115        Self {
116            noun: merge!(self.noun, other.noun),
117            pronoun: merge!(self.pronoun, other.pronoun),
118            verb: merge!(self.verb, other.verb),
119            adjective: merge!(self.adjective, other.adjective),
120            adverb: merge!(self.adverb, other.adverb),
121            conjunction: merge!(self.conjunction, other.conjunction),
122            dialects: self.dialects | other.dialects,
123            swear: self.swear.or(other.swear),
124            determiner: merge!(self.determiner, other.determiner),
125            preposition: self.preposition || other.preposition,
126            common: self.common || other.common,
127            derived_from: self.derived_from.or(other.derived_from),
128            pos_tag: self.pos_tag.or(other.pos_tag),
129            np_member: self.np_member.or(other.np_member),
130        }
131    }
132
133    /// Given a UPOS tag, discard any metadata that would disagree with the given POS tag.
134    /// For example, if the metadata suggests a word could either be a noun or an adjective, and we
135    /// provide a [`UPOS::NOUN`], this function will remove the adjective data.
136    ///
137    /// Additionally, if the metadata does not currently declare the potential of the word to be
138    /// the specific POS, it becomes so. That means if we provide a [`UPOS::ADJ`] to the function
139    /// for a metadata whose `Self::adjective = None`, it will become `Some`.
140    pub fn enforce_pos_exclusivity(&mut self, pos: &UPOS) {
141        use UPOS::*;
142        match pos {
143            NOUN => {
144                if let Some(noun) = self.noun {
145                    self.noun = Some(NounData {
146                        is_proper: Some(false),
147                        ..noun
148                    })
149                } else {
150                    self.noun = Some(NounData {
151                        is_proper: Some(false),
152                        is_singular: None,
153                        is_plural: None,
154                        is_countable: None,
155                        is_mass: None,
156                        is_possessive: None,
157                    })
158                }
159
160                self.pronoun = None;
161                self.verb = None;
162                self.adjective = None;
163                self.adverb = None;
164                self.conjunction = None;
165                self.determiner = None;
166                self.preposition = false;
167            }
168            PROPN => {
169                if let Some(noun) = self.noun {
170                    self.noun = Some(NounData {
171                        is_proper: Some(true),
172                        ..noun
173                    })
174                } else {
175                    self.noun = Some(NounData {
176                        is_proper: Some(true),
177                        is_singular: None,
178                        is_plural: None,
179                        is_countable: None,
180                        is_mass: None,
181                        is_possessive: None,
182                    })
183                }
184
185                self.pronoun = None;
186                self.verb = None;
187                self.adjective = None;
188                self.adverb = None;
189                self.conjunction = None;
190                self.determiner = None;
191                self.preposition = false;
192            }
193            PRON => {
194                if self.pronoun.is_none() {
195                    self.pronoun = Some(PronounData::default())
196                }
197
198                self.noun = None;
199                self.verb = None;
200                self.adjective = None;
201                self.adverb = None;
202                self.conjunction = None;
203                self.determiner = None;
204                self.preposition = false;
205            }
206            VERB => {
207                if let Some(verb) = self.verb {
208                    self.verb = Some(VerbData {
209                        is_auxiliary: Some(false),
210                        ..verb
211                    })
212                } else {
213                    self.verb = Some(VerbData {
214                        is_auxiliary: Some(false),
215                        ..Default::default()
216                    })
217                }
218
219                self.noun = None;
220                self.pronoun = None;
221                self.adjective = None;
222                self.adverb = None;
223                self.conjunction = None;
224                self.determiner = None;
225                self.preposition = false;
226            }
227            AUX => {
228                if let Some(verb) = self.verb {
229                    self.verb = Some(VerbData {
230                        is_auxiliary: Some(true),
231                        ..verb
232                    })
233                } else {
234                    self.verb = Some(VerbData {
235                        is_auxiliary: Some(true),
236                        ..Default::default()
237                    })
238                }
239
240                self.noun = None;
241                self.pronoun = None;
242                self.adjective = None;
243                self.adverb = None;
244                self.conjunction = None;
245                self.determiner = None;
246                self.preposition = false;
247            }
248            ADJ => {
249                if self.adjective.is_none() {
250                    self.adjective = Some(AdjectiveData::default())
251                }
252
253                self.noun = None;
254                self.pronoun = None;
255                self.verb = None;
256                self.adverb = None;
257                self.conjunction = None;
258                self.determiner = None;
259                self.preposition = false;
260            }
261            ADV => {
262                if self.adverb.is_none() {
263                    self.adverb = Some(AdverbData::default())
264                }
265
266                self.noun = None;
267                self.pronoun = None;
268                self.verb = None;
269                self.adjective = None;
270                self.conjunction = None;
271                self.determiner = None;
272                self.preposition = false;
273            }
274            ADP => {
275                self.noun = None;
276                self.pronoun = None;
277                self.verb = None;
278                self.adjective = None;
279                self.adverb = None;
280                self.conjunction = None;
281                self.determiner = None;
282                self.preposition = true;
283            }
284            DET => {
285                self.noun = None;
286                self.pronoun = None;
287                self.verb = None;
288                self.adjective = None;
289                self.adverb = None;
290                self.conjunction = None;
291                self.preposition = false;
292                self.determiner = Some(DeterminerData::default());
293            }
294            CCONJ | SCONJ => {
295                if self.conjunction.is_none() {
296                    self.conjunction = Some(ConjunctionData::default())
297                }
298
299                self.noun = None;
300                self.pronoun = None;
301                self.verb = None;
302                self.adjective = None;
303                self.adverb = None;
304                self.determiner = None;
305                self.preposition = false;
306            }
307            _ => {}
308        }
309    }
310
311    generate_metadata_queries!(
312        // Singular and countable default to true, so their metadata queries are not generated.
313        noun has proper, plural, mass, possessive.
314        pronoun has personal, singular, plural, possessive, reflexive, subject, object.
315        determiner has demonstrative, possessive.
316        verb has linking, auxiliary.
317        conjunction has.
318        adjective has.
319        adverb has
320    );
321
322    // Manual metadata queries
323
324    // Pronoun metadata queries
325
326    pub fn is_first_person_plural_pronoun(&self) -> bool {
327        matches!(
328            self.pronoun,
329            Some(PronounData {
330                person: Some(Person::First),
331                is_plural: Some(true),
332                ..
333            })
334        )
335    }
336
337    pub fn is_first_person_singular_pronoun(&self) -> bool {
338        matches!(
339            self.pronoun,
340            Some(PronounData {
341                person: Some(Person::First),
342                is_singular: Some(true),
343                ..
344            })
345        )
346    }
347
348    pub fn is_third_person_plural_pronoun(&self) -> bool {
349        matches!(
350            self.pronoun,
351            Some(PronounData {
352                person: Some(Person::Third),
353                is_plural: Some(true),
354                ..
355            })
356        )
357    }
358
359    pub fn is_third_person_singular_pronoun(&self) -> bool {
360        matches!(
361            self.pronoun,
362            Some(PronounData {
363                person: Some(Person::Third),
364                is_singular: Some(true),
365                ..
366            })
367        )
368    }
369
370    pub fn is_third_person_pronoun(&self) -> bool {
371        matches!(
372            self.pronoun,
373            Some(PronounData {
374                person: Some(Person::Third),
375                ..
376            })
377        )
378    }
379
380    pub fn is_second_person_pronoun(&self) -> bool {
381        matches!(
382            self.pronoun,
383            Some(PronounData {
384                person: Some(Person::Second),
385                ..
386            })
387        )
388    }
389
390    pub fn is_verb_lemma(&self) -> bool {
391        matches!(
392            self.verb,
393            Some(VerbData {
394                verb_form: Some(VerbForm::LemmaForm),
395                ..
396            })
397        )
398    }
399
400    pub fn is_verb_past_form(&self) -> bool {
401        matches!(
402            self.verb,
403            Some(VerbData {
404                verb_form: Some(VerbForm::PastForm),
405                ..
406            })
407        )
408    }
409
410    pub fn is_verb_progressive_form(&self) -> bool {
411        matches!(
412            self.verb,
413            Some(VerbData {
414                verb_form: Some(VerbForm::ProgressiveForm),
415                ..
416            })
417        )
418    }
419
420    pub fn is_verb_third_person_singular_present_form(&self) -> bool {
421        matches!(
422            self.verb,
423            Some(VerbData {
424                verb_form: Some(VerbForm::ThirdPersonSingularPresentForm),
425                ..
426            })
427        )
428    }
429
430    // Noun metadata queries
431
432    // Singular is default if number is not marked in the dictionary.
433    pub fn is_singular_noun(&self) -> bool {
434        if let Some(noun) = self.noun {
435            matches!(
436                (noun.is_singular, noun.is_plural),
437                (Some(true), _) | (None | Some(false), None | Some(false))
438            )
439        } else {
440            false
441        }
442    }
443    pub fn is_non_singular_noun(&self) -> bool {
444        if let Some(noun) = self.noun {
445            !matches!(
446                (noun.is_singular, noun.is_plural),
447                (Some(true), _) | (None | Some(false), None | Some(false))
448            )
449        } else {
450            false
451        }
452    }
453
454    // Countable is default if countability is not marked in the dictionary.
455    pub fn is_countable_noun(&self) -> bool {
456        if let Some(noun) = self.noun {
457            matches!(
458                (noun.is_countable, noun.is_mass),
459                (Some(true), _) | (None | Some(false), None | Some(false))
460            )
461        } else {
462            false
463        }
464    }
465    pub fn is_non_countable_noun(&self) -> bool {
466        if let Some(noun) = self.noun {
467            !matches!(
468                (noun.is_countable, noun.is_mass),
469                (Some(true), _) | (None | Some(false), None | Some(false))
470            )
471        } else {
472            false
473        }
474    }
475
476    // Nominal metadata queries (noun + pronoun)
477
478    /// Checks if the word is definitely nominal.
479    pub fn is_nominal(&self) -> bool {
480        self.is_noun() || self.is_pronoun()
481    }
482
483    /// Checks if the word is definitely a nominal and more specifically is labeled as (a) singular.
484    pub fn is_singular_nominal(&self) -> bool {
485        self.is_singular_noun() || self.is_singular_pronoun()
486    }
487
488    /// Checks if the word is definitely a nominal and more specifically is labeled as (a) plural.
489    pub fn is_plural_nominal(&self) -> bool {
490        self.is_plural_noun() || self.is_plural_pronoun()
491    }
492
493    /// Checks if the word is definitely a nominal and more specifically is labeled as (a) possessive.
494    pub fn is_possessive_nominal(&self) -> bool {
495        self.is_possessive_noun() || self.is_possessive_pronoun()
496    }
497
498    /// Checks if the word is definitely a nominal and more specifically is labeled as __not__ (a) singular.
499    pub fn is_non_singular_nominal(&self) -> bool {
500        self.is_non_singular_noun() || self.is_non_singular_pronoun()
501    }
502
503    /// Checks if the word is definitely a nominal and more specifically is labeled as __not__ (a) plural.
504    pub fn is_non_plural_nominal(&self) -> bool {
505        self.is_non_plural_noun() || self.is_non_plural_pronoun()
506    }
507
508    /// Checks if the word is definitely a nominal and more specifically is labeled as __not__ (a) possessive.
509    pub fn is_non_possessive_nominal(&self) -> bool {
510        self.is_non_possessive_noun() || self.is_non_possessive_pronoun()
511    }
512
513    /// Checks whether a word is _definitely_ a swear.
514    pub fn is_swear(&self) -> bool {
515        matches!(self.swear, Some(true))
516    }
517
518    /// Same thing as [`Self::or`], except in-place rather than a clone.
519    pub fn append(&mut self, other: &Self) -> &mut Self {
520        *self = self.or(other);
521        self
522    }
523}
524
525// These verb forms are morphological variations, distinct from TAM (Tense-Aspect-Mood)
526// Each form can be used in various TAM combinations:
527// - Lemma form (infinitive, citation form, dictionary form)
528//   Used in infinitives (e.g., "to sleep"), imperatives (e.g., "sleep!"), and with modals (e.g., "will sleep")
529// - Past form (past participle and simple past)
530//   Used as verbs (e.g., "slept") or adjectives (e.g., "closed door")
531// - Progressive form (present participle and gerund)
532//   Used as verbs (e.g., "sleeping"), nouns (e.g., "sleeping is important"), or adjectives (e.g., "sleeping dog")
533// - Third person singular present (-s/-es)
534//   Used for third person singular subjects (e.g., "he sleeps", "she reads")
535//
536// Important notes:
537// 1. English expresses time through auxiliary verbs, not verb form alone
538// 2. Irregular verbs can have different forms for past participle and simple past
539// 3. Future is always expressed through auxiliary verbs (e.g., "will sleep", "going to sleep")
540#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Is, Hash)]
541pub enum VerbForm {
542    LemmaForm,
543    PastForm,
544    ProgressiveForm,
545    ThirdPersonSingularPresentForm,
546}
547
548#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
549pub struct VerbData {
550    pub is_linking: Option<bool>,
551    pub is_auxiliary: Option<bool>,
552    pub verb_form: Option<VerbForm>,
553}
554
555impl VerbData {
556    /// Produce a copy of `self` with the known properties of `other` set.
557    pub fn or(&self, other: &Self) -> Self {
558        Self {
559            is_linking: self.is_linking.or(other.is_linking),
560            is_auxiliary: self.is_auxiliary.or(other.is_auxiliary),
561            verb_form: self.verb_form.or(other.verb_form),
562        }
563    }
564}
565
566// nouns can be both singular and plural: "aircraft", "biceps", "fish", "sheep"
567// TODO other noun properties may be worth adding:
568// TODO count vs mass; abstract
569#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
570pub struct NounData {
571    pub is_proper: Option<bool>,
572    pub is_singular: Option<bool>,
573    pub is_plural: Option<bool>,
574    pub is_countable: Option<bool>,
575    pub is_mass: Option<bool>,
576    pub is_possessive: Option<bool>,
577}
578
579impl NounData {
580    /// Produce a copy of `self` with the known properties of `other` set.
581    pub fn or(&self, other: &Self) -> Self {
582        Self {
583            is_proper: self.is_proper.or(other.is_proper),
584            is_singular: self.is_singular.or(other.is_singular),
585            is_plural: self.is_plural.or(other.is_plural),
586            is_countable: self.is_countable.or(other.is_countable),
587            is_mass: self.is_mass.or(other.is_mass),
588            is_possessive: self.is_possessive.or(other.is_possessive),
589        }
590    }
591}
592
593// Person is a property of pronouns; the verb 'be', plus all verbs reflect 3rd person singular with -s
594#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Is, Hash)]
595pub enum Person {
596    First,
597    Second,
598    Third,
599}
600
601// TODO for now focused on personal pronouns?
602#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
603pub struct PronounData {
604    pub is_personal: Option<bool>,
605    pub is_singular: Option<bool>,
606    pub is_plural: Option<bool>,
607    pub is_possessive: Option<bool>,
608    pub is_reflexive: Option<bool>,
609    pub person: Option<Person>,
610    pub is_subject: Option<bool>,
611    pub is_object: Option<bool>,
612}
613
614impl PronounData {
615    /// Produce a copy of `self` with the known properties of `other` set.
616    pub fn or(&self, other: &Self) -> Self {
617        Self {
618            is_personal: self.is_personal.or(other.is_personal),
619            is_singular: self.is_singular.or(other.is_singular),
620            is_plural: self.is_plural.or(other.is_plural),
621            is_possessive: self.is_possessive.or(other.is_possessive),
622            is_reflexive: self.is_reflexive.or(other.is_reflexive),
623            person: self.person.or(other.person),
624            is_subject: self.is_subject.or(other.is_subject),
625            is_object: self.is_object.or(other.is_object),
626        }
627    }
628}
629
630/// Additional metadata for determiners
631#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
632pub struct DeterminerData {
633    pub is_demonstrative: Option<bool>,
634    pub is_possessive: Option<bool>,
635}
636
637impl DeterminerData {
638    /// Produce a copy of `self` with the known properties of `other` set.
639    pub fn or(&self, other: &Self) -> Self {
640        Self {
641            is_demonstrative: self.is_demonstrative.or(other.is_demonstrative),
642            is_possessive: self.is_possessive.or(other.is_possessive),
643        }
644    }
645}
646
647/// Degree is a property of adjectives: positive is not inflected
648/// Comparative is inflected with -er or comes after the word "more"
649/// Superlative is inflected with -est or comes after the word "most"
650#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Is, Hash)]
651pub enum Degree {
652    Positive,
653    Comparative,
654    Superlative,
655}
656
657/// Some adjectives are not comparable so don't have -er or -est forms and can't be used with "more" or "most".
658/// Some adjectives can only be used "attributively" (before a noun); some only predicatively (after "is" etc.).
659/// In old grammars words like the articles and determiners are classified as adjectives but behave differently.
660#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
661pub struct AdjectiveData {
662    pub degree: Option<Degree>,
663}
664
665impl AdjectiveData {
666    /// Produce a copy of `self` with the known properties of `other` set.
667    pub fn or(&self, other: &Self) -> Self {
668        Self {
669            degree: self.degree.or(other.degree),
670        }
671    }
672}
673
674/// Adverb can be a "junk drawer" category for words which don't fit the other major categories.
675/// The typical adverbs are "adverbs of manner", those derived from adjectives in -ly
676/// other adverbs (time, place, etc) should probably not be considered adverbs for Harper's purposes
677#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
678pub struct AdverbData {}
679
680impl AdverbData {
681    /// Produce a copy of `self` with the known properties of `other` set.
682    pub fn or(&self, _other: &Self) -> Self {
683        Self {}
684    }
685}
686
687#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
688pub struct ConjunctionData {}
689
690impl ConjunctionData {
691    /// Produce a copy of `self` with the known properties of `other` set.
692    pub fn or(&self, _other: &Self) -> Self {
693        Self {}
694    }
695}
696
697/// A regional dialect.
698///
699/// Note: these have bit-shifted values so that they can ergonomically integrate with
700/// `DialectFlags`. Each value here must have a unique bit index inside
701/// `DialectsUnderlyingType`.
702#[derive(
703    Debug,
704    Clone,
705    Copy,
706    Serialize,
707    Deserialize,
708    PartialEq,
709    PartialOrd,
710    Eq,
711    Hash,
712    EnumCount,
713    EnumString,
714    Display,
715    VariantArray,
716)]
717pub enum Dialect {
718    American = 1 << 0,
719    Canadian = 1 << 1,
720    Australian = 1 << 2,
721    British = 1 << 3,
722}
723impl Dialect {
724    /// Tries to guess the dialect used in the document by finding which dialect is used the most.
725    /// Returns `None` if it fails to find a single dialect that is used the most.
726    #[must_use]
727    pub fn try_guess_from_document(document: &Document) -> Option<Self> {
728        Self::try_from(DialectFlags::get_most_used_dialects_from_document(document)).ok()
729    }
730
731    /// Tries to get a dialect from its abbreviation. Returns `None` if the abbreviation is not
732    /// recognized.
733    ///
734    /// # Examples
735    ///
736    /// ```
737    /// use harper_core::Dialect;
738    ///
739    /// let abbrs = ["US", "CA", "AU", "GB"];
740    /// let mut dialects = abbrs.iter().map(|abbr| Dialect::try_from_abbr(abbr));
741    ///
742    /// assert_eq!(Some(Dialect::American), dialects.next().unwrap()); // US
743    /// assert_eq!(Some(Dialect::Canadian), dialects.next().unwrap()); // CA
744    /// assert_eq!(Some(Dialect::Australian), dialects.next().unwrap()); // AU
745    /// assert_eq!(Some(Dialect::British), dialects.next().unwrap()); // GB
746    /// ```
747    #[must_use]
748    pub fn try_from_abbr(abbr: &str) -> Option<Self> {
749        match abbr {
750            "US" => Some(Self::American),
751            "CA" => Some(Self::Canadian),
752            "AU" => Some(Self::Australian),
753            "GB" => Some(Self::British),
754            _ => None,
755        }
756    }
757}
758impl TryFrom<DialectFlags> for Dialect {
759    type Error = ();
760
761    /// Attempts to convert `DialectFlags` to a single `Dialect`.
762    ///
763    /// # Errors
764    ///
765    /// Will return `Err` if more than one dialect is enabled or if an undefined dialect is
766    /// enabled.
767    fn try_from(dialect_flags: DialectFlags) -> Result<Self, Self::Error> {
768        // Ensure only one dialect is enabled before converting.
769        if dialect_flags.bits().count_ones() == 1 {
770            match dialect_flags {
771                df if df.is_dialect_enabled_strict(Dialect::American) => Ok(Dialect::American),
772                df if df.is_dialect_enabled_strict(Dialect::Canadian) => Ok(Dialect::Canadian),
773                df if df.is_dialect_enabled_strict(Dialect::Australian) => Ok(Dialect::Australian),
774                df if df.is_dialect_enabled_strict(Dialect::British) => Ok(Dialect::British),
775                _ => Err(()),
776            }
777        } else {
778            // More than one dialect enabled; can't soundly convert.
779            Err(())
780        }
781    }
782}
783
784// The underlying type used for DialectFlags.
785// At the time of writing, this is currently a `u8`. If we want to define more than 8 dialects in
786// the future, we will need to switch this to a larger type.
787type DialectFlagsUnderlyingType = u8;
788
789bitflags::bitflags! {
790    /// A collection of bit flags used to represent enabled dialects.
791    ///
792    /// This is generally used to allow a word (or similar) to be tagged with multiple dialects.
793    #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash)]
794    #[serde(transparent)]
795    pub struct DialectFlags: DialectFlagsUnderlyingType {
796        const AMERICAN = Dialect::American as DialectFlagsUnderlyingType;
797        const CANADIAN = Dialect::Canadian as DialectFlagsUnderlyingType;
798        const AUSTRALIAN = Dialect::Australian as DialectFlagsUnderlyingType;
799        const BRITISH = Dialect::British as DialectFlagsUnderlyingType;
800    }
801}
802impl DialectFlags {
803    /// Checks if the provided dialect is enabled.
804    /// If no dialect is explicitly enabled, it is assumed that all dialects are enabled.
805    #[must_use]
806    pub fn is_dialect_enabled(self, dialect: Dialect) -> bool {
807        self.is_empty() || self.intersects(Self::from_dialect(dialect))
808    }
809
810    /// Checks if the provided dialect is ***explicitly*** enabled.
811    ///
812    /// Unlike `is_dialect_enabled`, this will return false when no dialects are explicitly
813    /// enabled.
814    #[must_use]
815    pub fn is_dialect_enabled_strict(self, dialect: Dialect) -> bool {
816        self.intersects(Self::from_dialect(dialect))
817    }
818
819    /// Constructs a `DialectFlags` from the provided `Dialect`, with only that dialect being
820    /// enabled.
821    ///
822    /// # Panics
823    ///
824    /// This will panic if `dialect` represents a dialect that is not defined in
825    /// `DialectFlags`.
826    #[must_use]
827    pub fn from_dialect(dialect: Dialect) -> Self {
828        let Some(out) = Self::from_bits(dialect as DialectFlagsUnderlyingType) else {
829            panic!("The '{dialect}' dialect isn't defined in DialectFlags!");
830        };
831        out
832    }
833
834    /// Gets the most commonly used dialect(s) in the document.
835    ///
836    /// If multiple dialects are used equally often, they will all be enabled in the returned
837    /// `DialectFlags`. On the other hand, if there is a single dialect that is used the most, it
838    /// will be the only one enabled.
839    #[must_use]
840    pub fn get_most_used_dialects_from_document(document: &Document) -> Self {
841        // Initialize counters.
842        let mut dialect_counters: [(Dialect, usize); Dialect::COUNT] = Dialect::VARIANTS
843            .iter()
844            .map(|d| (*d, 0))
845            .collect_array()
846            .unwrap();
847
848        // Count word dialects.
849        document.iter_words().for_each(|w| {
850            if let TokenKind::Word(Some(word_metadata)) = &w.kind {
851                // If the token is a word, iterate though the dialects in `dialect_counters` and
852                // increment those counters where the word has the respective dialect enabled.
853                dialect_counters.iter_mut().for_each(|(dialect, count)| {
854                    if word_metadata.dialects.is_dialect_enabled(*dialect) {
855                        *count += 1;
856                    }
857                });
858            }
859        });
860
861        // Find max counter.
862        let max_counter = dialect_counters
863            .iter()
864            .map(|(_, count)| count)
865            .max()
866            .unwrap();
867        // Get and convert the collection of most used dialects into a `DialectFlags`.
868        dialect_counters
869            .into_iter()
870            .filter(|(_, count)| count == max_counter)
871            .fold(DialectFlags::empty(), |acc, dialect| {
872                // Fold most used dialects into `DialectFlags` via bitwise or.
873                acc | Self::from_dialect(dialect.0)
874            })
875    }
876}
877impl Default for DialectFlags {
878    /// A default value with no dialects explicitly enabled.
879    /// Implicitly, this state corresponds to all dialects being enabled.
880    fn default() -> Self {
881        Self::empty()
882    }
883}
884
885#[cfg(test)]
886mod tests {
887    use crate::WordMetadata;
888    use crate::spell::{Dictionary, FstDictionary};
889
890    // Helper function to get word metadata from the curated dictionary
891    fn md(word: &str) -> WordMetadata {
892        FstDictionary::curated()
893            .get_word_metadata_str(word)
894            .unwrap_or_else(|| panic!("Word '{word}' not found in dictionary"))
895            .clone()
896    }
897
898    mod dialect {
899        use super::super::{Dialect, DialectFlags};
900        use crate::Document;
901
902        #[test]
903        fn guess_british_dialect() {
904            let document = Document::new_plain_english_curated("Aluminium was used.");
905            let df = DialectFlags::get_most_used_dialects_from_document(&document);
906            assert!(
907                df.is_dialect_enabled_strict(Dialect::British)
908                    && !df.is_dialect_enabled_strict(Dialect::American)
909            );
910        }
911
912        #[test]
913        fn guess_american_dialect() {
914            let document = Document::new_plain_english_curated("Aluminum was used.");
915            let df = DialectFlags::get_most_used_dialects_from_document(&document);
916            assert!(
917                df.is_dialect_enabled_strict(Dialect::American)
918                    && !df.is_dialect_enabled_strict(Dialect::British)
919            );
920        }
921    }
922
923    mod noun {
924        use crate::word_metadata::tests::md;
925
926        #[test]
927        fn puppy_is_noun() {
928            assert!(md("puppy").is_noun());
929        }
930
931        #[test]
932        fn prepare_is_not_noun() {
933            assert!(!md("prepare").is_noun());
934        }
935
936        #[test]
937        fn paris_is_proper_noun() {
938            assert!(md("Paris").is_proper_noun());
939        }
940
941        #[test]
942        fn permit_is_non_proper_noun() {
943            assert!(md("lapdog").is_non_proper_noun());
944        }
945
946        #[test]
947        fn hound_is_singular_noun() {
948            assert!(md("hound").is_singular_noun());
949        }
950
951        #[test]
952        fn pooches_is_non_singular_noun() {
953            assert!(md("pooches").is_non_singular_noun());
954        }
955
956        // Make sure is_non_xxx_noun methods don't behave like is_not_xxx_noun.
957        // In other words, make sure they don't return true for words that are not nouns.
958        // They must only pass for words that are nouns but not singular etc.
959        #[test]
960        fn loyal_doesnt_pass_is_non_singular_noun() {
961            assert!(!md("loyal").is_non_singular_noun());
962        }
963
964        #[test]
965        fn hounds_is_plural_noun() {
966            assert!(md("hounds").is_plural_noun());
967        }
968
969        #[test]
970        fn pooch_is_non_plural_noun() {
971            assert!(md("pooch").is_non_plural_noun());
972        }
973
974        #[test]
975        fn fish_is_singular_noun() {
976            assert!(md("fish").is_singular_noun());
977        }
978
979        #[test]
980        fn fish_is_plural_noun() {
981            assert!(md("fish").is_plural_noun());
982        }
983
984        #[test]
985        fn fishes_is_plural_noun() {
986            assert!(md("fishes").is_plural_noun());
987        }
988
989        #[test]
990        fn sheep_is_singular_noun() {
991            assert!(md("sheep").is_singular_noun());
992        }
993
994        #[test]
995        fn sheep_is_plural_noun() {
996            assert!(md("sheep").is_plural_noun());
997        }
998
999        #[test]
1000        #[should_panic]
1001        fn sheeps_is_not_word() {
1002            md("sheeps");
1003        }
1004
1005        #[test]
1006        fn bicep_is_singular_noun() {
1007            assert!(md("bicep").is_singular_noun());
1008        }
1009
1010        #[test]
1011        fn biceps_is_singular_noun() {
1012            assert!(md("biceps").is_singular_noun());
1013        }
1014
1015        #[test]
1016        fn biceps_is_plural_noun() {
1017            assert!(md("biceps").is_plural_noun());
1018        }
1019
1020        #[test]
1021        fn aircraft_is_singular_noun() {
1022            assert!(md("aircraft").is_singular_noun());
1023        }
1024
1025        #[test]
1026        fn aircraft_is_plural_noun() {
1027            assert!(md("aircraft").is_plural_noun());
1028        }
1029
1030        #[test]
1031        #[should_panic]
1032        fn aircrafts_is_not_word() {
1033            md("aircrafts");
1034        }
1035
1036        #[test]
1037        fn dog_apostrophe_s_is_possessive_noun() {
1038            assert!(md("dog's").is_possessive_noun());
1039        }
1040
1041        #[test]
1042        fn dogs_is_non_possessive_noun() {
1043            assert!(md("dogs").is_non_possessive_noun());
1044        }
1045
1046        // noun countability
1047
1048        #[test]
1049        fn dog_is_countable() {
1050            assert!(md("dog").is_countable_noun());
1051        }
1052        #[test]
1053        fn dog_is_non_mass_noun() {
1054            assert!(md("dog").is_non_mass_noun());
1055        }
1056
1057        #[test]
1058        fn furniture_is_mass_noun() {
1059            assert!(md("furniture").is_mass_noun());
1060        }
1061        #[test]
1062        fn furniture_is_not_countable_noun() {
1063            assert!(md("furniture").is_non_countable_noun());
1064        }
1065
1066        #[test]
1067        fn beer_is_countable_noun() {
1068            assert!(md("beer").is_countable_noun());
1069        }
1070        #[test]
1071        fn beer_is_mass_noun() {
1072            assert!(md("beer").is_mass_noun());
1073        }
1074    }
1075
1076    mod pronoun {
1077        use crate::word_metadata::tests::md;
1078
1079        mod i_me_myself {
1080            use crate::word_metadata::tests::md;
1081
1082            #[test]
1083            fn i_is_pronoun() {
1084                assert!(md("I").is_pronoun());
1085            }
1086            #[test]
1087            fn i_is_personal_pronoun() {
1088                assert!(md("I").is_personal_pronoun());
1089            }
1090            #[test]
1091            fn i_is_singular_pronoun() {
1092                assert!(md("I").is_singular_pronoun());
1093            }
1094            #[test]
1095            fn i_is_subject_pronoun() {
1096                assert!(md("I").is_subject_pronoun());
1097            }
1098
1099            #[test]
1100            fn me_is_pronoun() {
1101                assert!(md("me").is_pronoun());
1102            }
1103            #[test]
1104            fn me_is_personal_pronoun() {
1105                assert!(md("me").is_personal_pronoun());
1106            }
1107            #[test]
1108            fn me_is_singular_pronoun() {
1109                assert!(md("me").is_singular_pronoun());
1110            }
1111            #[test]
1112            fn me_is_object_pronoun() {
1113                assert!(md("me").is_object_pronoun());
1114            }
1115
1116            #[test]
1117            fn myself_is_pronoun() {
1118                assert!(md("myself").is_pronoun());
1119            }
1120            #[test]
1121            fn myself_is_personal_pronoun() {
1122                assert!(md("myself").is_personal_pronoun());
1123            }
1124            #[test]
1125            fn myself_is_singular_pronoun() {
1126                assert!(md("myself").is_singular_pronoun());
1127            }
1128            #[test]
1129            fn myself_is_reflexive_pronoun() {
1130                assert!(md("myself").is_reflexive_pronoun());
1131            }
1132        }
1133
1134        mod we_us_ourselves {
1135            use crate::word_metadata::tests::md;
1136
1137            #[test]
1138            fn we_is_pronoun() {
1139                assert!(md("we").is_pronoun());
1140            }
1141            #[test]
1142            fn we_is_personal_pronoun() {
1143                assert!(md("we").is_personal_pronoun());
1144            }
1145            #[test]
1146            fn we_is_plural_pronoun() {
1147                assert!(md("we").is_plural_pronoun());
1148            }
1149            #[test]
1150            fn we_is_subject_pronoun() {
1151                assert!(md("we").is_subject_pronoun());
1152            }
1153
1154            #[test]
1155            fn us_is_pronoun() {
1156                assert!(md("us").is_pronoun());
1157            }
1158            #[test]
1159            fn us_is_personal_pronoun() {
1160                assert!(md("us").is_personal_pronoun());
1161            }
1162            #[test]
1163            fn us_is_plural_pronoun() {
1164                assert!(md("us").is_plural_pronoun());
1165            }
1166            #[test]
1167            fn us_is_object_pronoun() {
1168                assert!(md("us").is_object_pronoun());
1169            }
1170
1171            #[test]
1172            fn ourselves_is_pronoun() {
1173                assert!(md("ourselves").is_pronoun());
1174            }
1175            #[test]
1176            fn ourselves_is_personal_pronoun() {
1177                assert!(md("ourselves").is_personal_pronoun());
1178            }
1179            #[test]
1180            fn ourselves_is_plural_pronoun() {
1181                assert!(md("ourselves").is_plural_pronoun());
1182            }
1183            #[test]
1184            fn ourselves_is_reflexive_pronoun() {
1185                assert!(md("ourselves").is_reflexive_pronoun());
1186            }
1187        }
1188
1189        mod you_yourself {
1190            use crate::word_metadata::tests::md;
1191
1192            #[test]
1193            fn you_is_pronoun() {
1194                assert!(md("you").is_pronoun());
1195            }
1196            #[test]
1197            fn you_is_personal_pronoun() {
1198                assert!(md("you").is_personal_pronoun());
1199            }
1200            #[test]
1201            fn you_is_singular_pronoun() {
1202                assert!(md("you").is_singular_pronoun());
1203            }
1204            #[test]
1205            fn you_is_plural_pronoun() {
1206                assert!(md("you").is_plural_pronoun());
1207            }
1208            #[test]
1209            fn you_is_subject_pronoun() {
1210                assert!(md("you").is_subject_pronoun());
1211            }
1212            #[test]
1213            fn you_is_object_pronoun() {
1214                assert!(md("you").is_object_pronoun());
1215            }
1216            #[test]
1217            fn yourself_is_pronoun() {
1218                assert!(md("yourself").is_pronoun());
1219            }
1220            #[test]
1221            fn yourself_is_personal_pronoun() {
1222                assert!(md("yourself").is_personal_pronoun());
1223            }
1224            #[test]
1225            fn yourself_is_singular_pronoun() {
1226                assert!(md("yourself").is_singular_pronoun());
1227            }
1228            #[test]
1229            fn yourself_is_reflexive_pronoun() {
1230                assert!(md("yourself").is_reflexive_pronoun());
1231            }
1232        }
1233
1234        mod he_him_himself {
1235            use crate::word_metadata::tests::md;
1236
1237            #[test]
1238            fn he_is_pronoun() {
1239                assert!(md("he").is_pronoun());
1240            }
1241            #[test]
1242            fn he_is_personal_pronoun() {
1243                assert!(md("he").is_personal_pronoun());
1244            }
1245            #[test]
1246            fn he_is_singular_pronoun() {
1247                assert!(md("he").is_singular_pronoun());
1248            }
1249            #[test]
1250            fn he_is_subject_pronoun() {
1251                assert!(md("he").is_subject_pronoun());
1252            }
1253
1254            #[test]
1255            fn him_is_pronoun() {
1256                assert!(md("him").is_pronoun());
1257            }
1258            #[test]
1259            fn him_is_personal_pronoun() {
1260                assert!(md("him").is_personal_pronoun());
1261            }
1262            #[test]
1263            fn him_is_singular_pronoun() {
1264                assert!(md("him").is_singular_pronoun());
1265            }
1266            #[test]
1267            fn him_is_object_pronoun() {
1268                assert!(md("him").is_object_pronoun());
1269            }
1270
1271            #[test]
1272            fn himself_is_pronoun() {
1273                assert!(md("himself").is_pronoun());
1274            }
1275            #[test]
1276            fn himself_is_personal_pronoun() {
1277                assert!(md("himself").is_personal_pronoun());
1278            }
1279            #[test]
1280            fn himself_is_singular_pronoun() {
1281                assert!(md("himself").is_singular_pronoun());
1282            }
1283            #[test]
1284            fn himself_is_reflexive_pronoun() {
1285                assert!(md("himself").is_reflexive_pronoun());
1286            }
1287        }
1288
1289        mod she_her_herself {
1290            use crate::word_metadata::tests::md;
1291
1292            #[test]
1293            fn she_is_pronoun() {
1294                assert!(md("she").is_pronoun());
1295            }
1296            #[test]
1297            fn she_is_personal_pronoun() {
1298                assert!(md("she").is_personal_pronoun());
1299            }
1300            #[test]
1301            fn she_is_singular_pronoun() {
1302                assert!(md("she").is_singular_pronoun());
1303            }
1304            #[test]
1305            fn she_is_subject_pronoun() {
1306                assert!(md("she").is_subject_pronoun());
1307            }
1308
1309            #[test]
1310            fn her_is_pronoun() {
1311                assert!(md("her").is_pronoun());
1312            }
1313            #[test]
1314            fn her_is_personal_pronoun() {
1315                assert!(md("her").is_personal_pronoun());
1316            }
1317            #[test]
1318            fn her_is_singular_pronoun() {
1319                assert!(md("her").is_singular_pronoun());
1320            }
1321            #[test]
1322            fn her_is_object_pronoun() {
1323                assert!(md("her").is_object_pronoun());
1324            }
1325
1326            #[test]
1327            fn herself_is_pronoun() {
1328                assert!(md("herself").is_pronoun());
1329            }
1330            #[test]
1331            fn herself_is_personal_pronoun() {
1332                assert!(md("herself").is_personal_pronoun());
1333            }
1334            #[test]
1335            fn herself_is_singular_pronoun() {
1336                assert!(md("herself").is_singular_pronoun());
1337            }
1338            #[test]
1339            fn herself_is_reflexive_pronoun() {
1340                assert!(md("herself").is_reflexive_pronoun());
1341            }
1342        }
1343
1344        mod it_itself {
1345            use crate::word_metadata::tests::md;
1346
1347            #[test]
1348            fn it_is_pronoun() {
1349                assert!(md("it").is_pronoun());
1350            }
1351            #[test]
1352            fn it_is_personal_pronoun() {
1353                assert!(md("it").is_personal_pronoun());
1354            }
1355            #[test]
1356            fn it_is_singular_pronoun() {
1357                assert!(md("it").is_singular_pronoun());
1358            }
1359            #[test]
1360            fn it_is_subject_pronoun() {
1361                assert!(md("it").is_subject_pronoun());
1362            }
1363            #[test]
1364            fn it_is_object_pronoun() {
1365                assert!(md("it").is_object_pronoun());
1366            }
1367
1368            #[test]
1369            fn itself_is_pronoun() {
1370                assert!(md("itself").is_pronoun());
1371            }
1372            #[test]
1373            fn itself_is_personal_pronoun() {
1374                assert!(md("itself").is_personal_pronoun());
1375            }
1376            #[test]
1377            fn itself_is_singular_pronoun() {
1378                assert!(md("itself").is_singular_pronoun());
1379            }
1380            #[test]
1381            fn itself_is_reflexive_pronoun() {
1382                assert!(md("itself").is_reflexive_pronoun());
1383            }
1384        }
1385
1386        mod they_them_themselves {
1387            use crate::word_metadata::tests::md;
1388
1389            #[test]
1390            fn they_is_pronoun() {
1391                assert!(md("they").is_pronoun());
1392            }
1393            #[test]
1394            fn they_is_personal_pronoun() {
1395                assert!(md("they").is_personal_pronoun());
1396            }
1397            #[test]
1398            fn they_is_plural_pronoun() {
1399                assert!(md("they").is_plural_pronoun());
1400            }
1401            #[test]
1402            fn they_is_subject_pronoun() {
1403                assert!(md("they").is_subject_pronoun());
1404            }
1405
1406            #[test]
1407            fn them_is_pronoun() {
1408                assert!(md("them").is_pronoun());
1409            }
1410            #[test]
1411            fn them_is_personal_pronoun() {
1412                assert!(md("them").is_personal_pronoun());
1413            }
1414            #[test]
1415            fn them_is_plural_pronoun() {
1416                assert!(md("them").is_plural_pronoun());
1417            }
1418            #[test]
1419            fn them_is_object_pronoun() {
1420                assert!(md("them").is_object_pronoun());
1421            }
1422
1423            #[test]
1424            fn themselves_is_pronoun() {
1425                assert!(md("themselves").is_pronoun());
1426            }
1427            #[test]
1428            fn themselves_is_personal_pronoun() {
1429                assert!(md("themselves").is_personal_pronoun());
1430            }
1431            #[test]
1432            fn themselves_is_plural_pronoun() {
1433                assert!(md("themselves").is_plural_pronoun());
1434            }
1435            #[test]
1436            fn themselves_is_reflexive_pronoun() {
1437                assert!(md("themselves").is_reflexive_pronoun());
1438            }
1439        }
1440
1441        // Possessive pronouns (not to be confused with possessive adjectives/determiners)
1442        #[test]
1443        fn mine_is_pronoun() {
1444            assert!(md("mine").is_pronoun());
1445        }
1446        #[test]
1447        fn ours_is_pronoun() {
1448            assert!(md("ours").is_pronoun());
1449        }
1450        #[test]
1451        fn yours_is_pronoun() {
1452            assert!(md("yours").is_pronoun());
1453        }
1454        #[test]
1455        fn his_is_pronoun() {
1456            assert!(md("his").is_pronoun());
1457        }
1458        #[test]
1459        fn hers_is_pronoun() {
1460            assert!(md("hers").is_pronoun());
1461        }
1462        #[test]
1463        fn its_is_pronoun() {
1464            assert!(md("its").is_pronoun());
1465        }
1466        #[test]
1467        fn theirs_is_pronoun() {
1468            assert!(md("theirs").is_pronoun());
1469        }
1470
1471        // archaic pronouns
1472        #[test]
1473        fn archaic_pronouns() {
1474            assert!(md("thou").is_pronoun());
1475            assert!(md("thee").is_pronoun());
1476            assert!(md("thyself").is_pronoun());
1477            assert!(md("thine").is_pronoun());
1478        }
1479
1480        // generic pronouns
1481        #[test]
1482        fn generic_pronouns() {
1483            assert!(md("one").is_pronoun());
1484            assert!(md("oneself").is_pronoun());
1485        }
1486
1487        // relative and interrogative pronouns
1488        #[test]
1489        fn relative_and_interrogative_pronouns() {
1490            assert!(md("who").is_pronoun());
1491            assert!(md("whom").is_pronoun());
1492            assert!(md("whose").is_pronoun());
1493            assert!(md("which").is_pronoun());
1494            assert!(md("what").is_pronoun());
1495        }
1496
1497        // nonstandard pronouns
1498        #[test]
1499        #[ignore = "not in dictionary"]
1500        fn nonstandard_pronouns() {
1501            assert!(md("themself").pronoun.is_some());
1502            assert!(md("y'all'").pronoun.is_some());
1503        }
1504    }
1505
1506    #[test]
1507    fn the_is_determiner() {
1508        assert!(md("the").is_determiner());
1509    }
1510    #[test]
1511    fn this_is_demonstrative_determiner() {
1512        assert!(md("this").is_demonstrative_determiner());
1513    }
1514    #[test]
1515    fn your_is_possessive_determiner() {
1516        assert!(md("your").is_possessive_determiner());
1517    }
1518
1519    #[test]
1520    fn equipment_is_mass_noun() {
1521        assert!(md("equipment").is_mass_noun());
1522    }
1523
1524    #[test]
1525    fn equipment_is_non_countable_noun() {
1526        assert!(md("equipment").is_non_countable_noun());
1527    }
1528
1529    #[test]
1530    fn equipment_isnt_countable_noun() {
1531        assert!(!md("equipment").is_countable_noun());
1532    }
1533}