Skip to main content

harper_core/
dict_word_metadata.rs

1use harper_brill::UPOS;
2use is_macro::Is;
3use itertools::Itertools;
4use paste::paste;
5use serde::{Deserialize, Serialize};
6use smallvec::SmallVec;
7use strum::{EnumCount as _, VariantArray as _};
8use strum_macros::{Display, EnumCount, EnumIter, EnumString, VariantArray};
9
10use std::convert::TryFrom;
11
12use crate::dict_word_metadata_orthography::OrthFlags;
13use crate::spell::WordId;
14use crate::{Document, TokenKind, TokenStringExt};
15
16/// This represents a "lexeme" or "headword" which is case-folded but affix-expanded.
17/// So not only lemmata but also inflected forms are stored here, with "horn" and "horns" each
18/// having their own lexeme, but "Ivy" and "ivy" sharing the same lexeme.
19#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Hash)]
20pub struct DictWordMetadata {
21    /// The main parts of speech which have extra data.
22    pub noun: Option<NounData>,
23    pub pronoun: Option<PronounData>,
24    pub verb: Option<VerbData>,
25    pub adjective: Option<AdjectiveData>,
26    pub adverb: Option<AdverbData>,
27    pub conjunction: Option<ConjunctionData>,
28    pub determiner: Option<DeterminerData>,
29    pub affix: Option<AffixData>,
30    /// Parts of speech which don't have extra data.
31    /// Whether the word is a [preposition](https://www.merriam-webster.com/dictionary/preposition).
32    #[serde(default = "default_false")]
33    pub preposition: bool,
34    /// Whether the word is an offensive word.
35    pub swear: Option<bool>,
36    /// Whether the word is an abbreviation of any kind.
37    pub abbreviation: Option<bool>,
38    /// The dialects this word belongs to.
39    /// If no dialects are defined, it can be assumed that the word is
40    /// valid in all dialects of English.
41    #[serde(default = "default_default")]
42    pub dialects: DialectFlags,
43    /// Orthographic information: letter case, spaces, hyphens, etc.
44    #[serde(default = "OrthFlags::empty")]
45    pub orth_info: OrthFlags,
46    /// Whether the word is considered especially common.
47    #[serde(default = "default_false")]
48    pub common: bool,
49    #[serde(default = "default_none")]
50    pub derived_from: Option<WordId>,
51    /// Generated by a chunker. Declares whether the word is a member of a nominal phrase. Using
52    /// this should be preferred over the similarly named `Pattern`.
53    ///
54    /// For more details, see [the announcement blog post](https://elijahpotter.dev/articles/training_a_chunker_with_burn).
55    pub np_member: Option<bool>,
56    /// Generated by a POS tagger. Declares what it inferred the word's part of speech to be.
57    pub pos_tag: Option<UPOS>,
58}
59
60/// Needed for `serde`
61fn default_false() -> bool {
62    false
63}
64
65/// Needed for `serde`
66fn default_none<T>() -> Option<T> {
67    None
68}
69
70/// Needed for `serde`
71fn default_default<T: Default>() -> T {
72    T::default()
73}
74
75macro_rules! generate_metadata_queries {
76    ($($category:ident has $($sub:ident),*).*) => {
77        paste! {
78            pub fn is_likely_homograph(&self) -> bool {
79                [self.is_determiner(), self.preposition, $(
80                    self.[< is_ $category >](),
81                )*].iter().map(|b| *b as u8).sum::<u8>() > 1
82            }
83
84            /// How different is this word from another?
85            pub fn difference(&self, other: &Self) -> u32 {
86                [
87                    $(
88                        Self::[< is_ $category >],
89                        $(
90                            Self::[< is_ $sub _ $category >],
91                            Self::[< is_non_ $sub _ $category >],
92                        )*
93                    )*
94                ]
95                .iter()
96                .fold(0, |acc, func| acc + (func(self) ^ func(other)) as u32)
97            }
98
99            $(
100                #[doc = concat!("Checks if the word is definitely a ", stringify!($category), ".")]
101                pub fn [< is_ $category >](&self) -> bool {
102                    self.$category.is_some()
103                }
104
105                $(
106                    #[doc = concat!("Checks if the word is definitely a ", stringify!($category), " and more specifically is labeled as (a) ", stringify!($sub), ".")]
107                    pub fn [< is_ $sub _ $category >](&self) -> bool {
108                        matches!(
109                            self.$category,
110                            Some([< $category:camel Data >]{
111                                [< is_ $sub >]: Some(true),
112                                ..
113                            })
114                        ) }
115
116                    #[doc = concat!("Checks if the word is definitely a ", stringify!($category), " and more specifically is labeled as __not__ (a) ", stringify!($sub), ".")]
117                    pub fn [< is_non_ $sub _ $category >](&self) -> bool {
118                        matches!(
119                            self.$category,
120                            Some([< $category:camel Data >]{
121                                [< is_ $sub >]: None | Some(false),
122                                ..
123                            })
124                        )
125                    }
126                )*
127            )*
128        }
129    };
130}
131
132impl DictWordMetadata {
133    /// If there is only one possible interpretation of the metadata, infer its UPOS tag.
134    pub fn infer_pos_tag(&self) -> Option<UPOS> {
135        // If an explicit POS tag exists, return it immediately.
136        if let Some(pos) = self.pos_tag {
137            return Some(pos);
138        }
139
140        // Collect all possible POS tags from metadata
141        let mut candidates = SmallVec::<[UPOS; 14]>::with_capacity(14);
142
143        if self.is_proper_noun() {
144            candidates.push(UPOS::PROPN);
145        }
146
147        if self.is_pronoun() {
148            candidates.push(UPOS::PRON);
149        }
150        if self.is_noun() {
151            candidates.push(UPOS::NOUN);
152        }
153        if self.is_verb() {
154            // Distinguish auxiliary verbs
155            if let Some(data) = &self.verb {
156                if data.is_auxiliary == Some(true) {
157                    candidates.push(UPOS::AUX);
158                } else {
159                    candidates.push(UPOS::VERB);
160                }
161            } else {
162                candidates.push(UPOS::VERB);
163            }
164        }
165        if self.is_adjective() {
166            candidates.push(UPOS::ADJ);
167        }
168        if self.is_adverb() {
169            candidates.push(UPOS::ADV);
170        }
171        if self.is_conjunction() {
172            candidates.push(UPOS::CCONJ);
173        }
174        if self.is_determiner() {
175            candidates.push(UPOS::DET);
176        }
177        if self.preposition {
178            candidates.push(UPOS::ADP);
179        }
180
181        // Remove duplicates
182        candidates.sort();
183        candidates.dedup();
184
185        candidates.into_iter().exactly_one().ok()
186    }
187
188    /// Produce a copy of `self` with the known properties of `other` set.
189    pub fn or(&self, other: &Self) -> Self {
190        let mut clone = self.clone();
191        clone.merge(other);
192        clone
193    }
194
195    /// Given a UPOS tag, discard any metadata that would disagree with the given POS tag.
196    /// For example, if the metadata suggests a word could either be a noun or an adjective, and we
197    /// provide a [`UPOS::NOUN`], this function will remove the adjective data.
198    ///
199    /// Additionally, if the metadata does not currently declare the potential of the word to be
200    /// the specific POS, it becomes so. That means if we provide a [`UPOS::ADJ`] to the function
201    /// for a metadata whose `Self::adjective = None`, it will become `Some`.
202    pub fn enforce_pos_exclusivity(&mut self, pos: &UPOS) {
203        use UPOS::*;
204        match pos {
205            NOUN => {
206                if let Some(noun) = self.noun {
207                    self.noun = Some(NounData {
208                        is_proper: Some(false),
209                        ..noun
210                    })
211                } else {
212                    self.noun = Some(NounData {
213                        is_proper: Some(false),
214                        is_singular: None,
215                        is_plural: None,
216                        is_countable: None,
217                        is_mass: None,
218                        is_possessive: None,
219                    })
220                }
221
222                self.pronoun = None;
223                self.verb = None;
224                self.adjective = None;
225                self.adverb = None;
226                self.conjunction = None;
227                self.determiner = None;
228                self.affix = None;
229                self.preposition = false;
230            }
231            PROPN => {
232                if let Some(noun) = self.noun {
233                    self.noun = Some(NounData {
234                        is_proper: Some(true),
235                        ..noun
236                    })
237                } else {
238                    self.noun = Some(NounData {
239                        is_proper: Some(true),
240                        is_singular: None,
241                        is_plural: None,
242                        is_countable: None,
243                        is_mass: None,
244                        is_possessive: None,
245                    })
246                }
247
248                self.pronoun = None;
249                self.verb = None;
250                self.adjective = None;
251                self.adverb = None;
252                self.conjunction = None;
253                self.determiner = None;
254                self.affix = None;
255                self.preposition = false;
256            }
257            PRON => {
258                if self.pronoun.is_none() {
259                    self.pronoun = Some(PronounData::default())
260                }
261
262                self.noun = None;
263                self.verb = None;
264                self.adjective = None;
265                self.adverb = None;
266                self.conjunction = None;
267                self.determiner = None;
268                self.affix = None;
269                self.preposition = false;
270            }
271            VERB => {
272                if let Some(verb) = self.verb {
273                    self.verb = Some(VerbData {
274                        is_auxiliary: Some(false),
275                        ..verb
276                    })
277                } else {
278                    self.verb = Some(VerbData {
279                        is_auxiliary: Some(false),
280                        ..Default::default()
281                    })
282                }
283
284                self.noun = None;
285                self.pronoun = None;
286                self.adjective = None;
287                self.adverb = None;
288                self.conjunction = None;
289                self.determiner = None;
290                self.affix = None;
291                self.preposition = false;
292            }
293            AUX => {
294                if let Some(verb) = self.verb {
295                    self.verb = Some(VerbData {
296                        is_auxiliary: Some(true),
297                        ..verb
298                    })
299                } else {
300                    self.verb = Some(VerbData {
301                        is_auxiliary: Some(true),
302                        ..Default::default()
303                    })
304                }
305
306                self.noun = None;
307                self.pronoun = None;
308                self.adjective = None;
309                self.adverb = None;
310                self.conjunction = None;
311                self.determiner = None;
312                self.affix = None;
313                self.preposition = false;
314            }
315            ADJ => {
316                if self.adjective.is_none() {
317                    self.adjective = Some(AdjectiveData::default())
318                }
319
320                self.noun = None;
321                self.pronoun = None;
322                self.verb = None;
323                self.adverb = None;
324                self.conjunction = None;
325                self.determiner = None;
326                self.affix = None;
327                self.preposition = false;
328            }
329            ADV => {
330                if self.adverb.is_none() {
331                    self.adverb = Some(AdverbData::default())
332                }
333
334                self.noun = None;
335                self.pronoun = None;
336                self.verb = None;
337                self.adjective = None;
338                self.conjunction = None;
339                self.determiner = None;
340                self.affix = None;
341                self.preposition = false;
342            }
343            ADP => {
344                self.noun = None;
345                self.pronoun = None;
346                self.verb = None;
347                self.adjective = None;
348                self.adverb = None;
349                self.conjunction = None;
350                self.determiner = None;
351                self.affix = None;
352                self.preposition = true;
353            }
354            DET => {
355                self.noun = None;
356                self.pronoun = None;
357                self.verb = None;
358                self.adjective = None;
359                self.adverb = None;
360                self.conjunction = None;
361                self.affix = None;
362                self.preposition = false;
363                self.determiner = Some(DeterminerData::default());
364            }
365            CCONJ | SCONJ => {
366                if self.conjunction.is_none() {
367                    self.conjunction = Some(ConjunctionData::default())
368                }
369
370                self.noun = None;
371                self.pronoun = None;
372                self.verb = None;
373                self.adjective = None;
374                self.adverb = None;
375                self.determiner = None;
376                self.affix = None;
377                self.preposition = false;
378            }
379            _ => {}
380        }
381    }
382
383    generate_metadata_queries!(
384        // Singular and countable default to true, so their metadata queries are not generated.
385        noun has proper, plural, mass, possessive.
386        pronoun has personal, singular, plural, possessive, reflexive, subject, object.
387        determiner has demonstrative, possessive, quantifier.
388        verb has linking, auxiliary.
389        conjunction has.
390        adjective has.
391        adverb has manner, frequency, degree
392    );
393
394    // Manual metadata queries
395
396    // Pronoun metadata queries
397
398    pub fn get_person(&self) -> Option<Person> {
399        self.pronoun.as_ref().and_then(|p| p.person)
400    }
401
402    pub fn is_first_person_plural_pronoun(&self) -> bool {
403        matches!(
404            self.pronoun,
405            Some(PronounData {
406                person: Some(Person::First),
407                is_plural: Some(true),
408                ..
409            })
410        )
411    }
412
413    pub fn is_first_person_singular_pronoun(&self) -> bool {
414        matches!(
415            self.pronoun,
416            Some(PronounData {
417                person: Some(Person::First),
418                is_singular: Some(true),
419                ..
420            })
421        )
422    }
423
424    pub fn is_third_person_plural_pronoun(&self) -> bool {
425        matches!(
426            self.pronoun,
427            Some(PronounData {
428                person: Some(Person::Third),
429                is_plural: Some(true),
430                ..
431            })
432        )
433    }
434
435    pub fn is_third_person_singular_pronoun(&self) -> bool {
436        matches!(
437            self.pronoun,
438            Some(PronounData {
439                person: Some(Person::Third),
440                is_singular: Some(true),
441                ..
442            })
443        )
444    }
445
446    pub fn is_third_person_pronoun(&self) -> bool {
447        matches!(
448            self.pronoun,
449            Some(PronounData {
450                person: Some(Person::Third),
451                ..
452            })
453        )
454    }
455
456    pub fn is_second_person_pronoun(&self) -> bool {
457        matches!(
458            self.pronoun,
459            Some(PronounData {
460                person: Some(Person::Second),
461                ..
462            })
463        )
464    }
465
466    // Lemma is default if no verb form is specified in the dictionary
467    pub fn is_verb_lemma(&self) -> bool {
468        if let Some(verb) = self.verb {
469            if let Some(forms) = verb.verb_forms {
470                return forms.is_empty() || forms.contains(VerbFormFlags::LEMMA);
471            } else {
472                return true;
473            }
474        }
475        false
476    }
477
478    pub fn is_verb_past_form(&self) -> bool {
479        self.verb.is_some_and(|v| {
480            v.verb_forms
481                .is_some_and(|vf| vf.contains(VerbFormFlags::PAST))
482        })
483    }
484
485    pub fn is_verb_regular_past_form(&self) -> bool {
486        self.verb.is_some_and(|v| {
487            v.verb_forms.is_some_and(|vf| {
488                vf.contains(VerbFormFlags::PRETERITE) && vf.contains(VerbFormFlags::PAST_PARTICIPLE)
489            })
490        })
491    }
492
493    pub fn is_verb_simple_past_form(&self) -> bool {
494        self.verb.is_some_and(|v| {
495            v.verb_forms
496                .is_some_and(|vf| vf.contains(VerbFormFlags::PRETERITE))
497        })
498    }
499
500    pub fn is_verb_past_participle_form(&self) -> bool {
501        self.verb.is_some_and(|v| {
502            v.verb_forms
503                .is_some_and(|vf| vf.contains(VerbFormFlags::PAST_PARTICIPLE))
504        })
505    }
506
507    pub fn is_verb_simple_past_only(&self) -> bool {
508        self.verb.is_some_and(|v| {
509            v.verb_forms.is_some_and(|vf| {
510                vf.contains(VerbFormFlags::PRETERITE)
511                    && !vf.intersects(VerbFormFlags::PAST | VerbFormFlags::PAST_PARTICIPLE)
512            })
513        })
514    }
515
516    pub fn is_verb_past_participle_only(&self) -> bool {
517        self.verb.is_some_and(|v| {
518            v.verb_forms.is_some_and(|vf| {
519                vf.contains(VerbFormFlags::PAST_PARTICIPLE)
520                    && !vf.intersects(VerbFormFlags::PAST | VerbFormFlags::PRETERITE)
521            })
522        })
523    }
524
525    pub fn is_verb_progressive_form(&self) -> bool {
526        self.verb.is_some_and(|v| {
527            v.verb_forms
528                .is_some_and(|vf| vf.contains(VerbFormFlags::PROGRESSIVE))
529        })
530    }
531
532    pub fn is_verb_third_person_singular_present_form(&self) -> bool {
533        self.verb.is_some_and(|v| {
534            v.verb_forms
535                .is_some_and(|vf| vf.contains(VerbFormFlags::THIRD_PERSON_SINGULAR))
536        })
537    }
538
539    // Noun metadata queries
540
541    // Singular is default if number is not marked in the dictionary.
542    pub fn is_singular_noun(&self) -> bool {
543        if let Some(noun) = self.noun {
544            matches!(
545                (noun.is_singular, noun.is_plural),
546                (Some(true), _) | (None | Some(false), None | Some(false))
547            )
548        } else {
549            false
550        }
551    }
552    pub fn is_non_singular_noun(&self) -> bool {
553        if let Some(noun) = self.noun {
554            !matches!(
555                (noun.is_singular, noun.is_plural),
556                (Some(true), _) | (None | Some(false), None | Some(false))
557            )
558        } else {
559            false
560        }
561    }
562
563    // Countable is default if countability is not marked in the dictionary.
564    pub fn is_countable_noun(&self) -> bool {
565        if let Some(noun) = self.noun {
566            matches!(
567                (noun.is_countable, noun.is_mass),
568                (Some(true), _) | (None | Some(false), None | Some(false))
569            )
570        } else {
571            false
572        }
573    }
574    pub fn is_non_countable_noun(&self) -> bool {
575        if let Some(noun) = self.noun {
576            !matches!(
577                (noun.is_countable, noun.is_mass),
578                (Some(true), _) | (None | Some(false), None | Some(false))
579            )
580        } else {
581            false
582        }
583    }
584
585    pub fn is_singular_noun_only(&self) -> bool {
586        if let Some(noun) = self.noun {
587            matches!(
588                (noun.is_singular, noun.is_plural),
589                (Some(true), None | Some(false))
590            )
591        } else {
592            false
593        }
594    }
595
596    pub fn is_plural_noun_only(&self) -> bool {
597        if let Some(noun) = self.noun {
598            matches!(
599                (noun.is_singular, noun.is_plural),
600                (None | Some(false), Some(true))
601            )
602        } else {
603            false
604        }
605    }
606
607    // Most mass nouns also have countable senses. Match those that are only mass nouns.
608    pub fn is_mass_noun_only(&self) -> bool {
609        if let Some(noun) = self.noun {
610            matches!(
611                (noun.is_countable, noun.is_mass),
612                (None | Some(false), Some(true))
613            )
614        } else {
615            false
616        }
617    }
618
619    // Nominal metadata queries (noun + pronoun)
620
621    /// Checks if the word is definitely nominal.
622    pub fn is_nominal(&self) -> bool {
623        self.is_noun() || self.is_pronoun()
624    }
625
626    /// Checks if the word is definitely a nominal and more specifically is labeled as (a) singular.
627    pub fn is_singular_nominal(&self) -> bool {
628        self.is_singular_noun() || self.is_singular_pronoun()
629    }
630
631    /// Checks if the word is definitely a nominal and more specifically is labeled as (a) plural.
632    pub fn is_plural_nominal(&self) -> bool {
633        self.is_plural_noun() || self.is_plural_pronoun()
634    }
635
636    /// Checks if the word is definitely a nominal and more specifically is labeled as (a) possessive.
637    /// NOTE: `possessive pronoun`s are not qualifiers, but words like `mine`, `yours`, etc.
638    /// The terminology of `possessive noun`, `possessive pronoun` and `possessive determiner` only
639    /// tends to reinforce this confusion.
640    pub fn is_possessive_nominal(&self) -> bool {
641        self.is_possessive_noun() || self.is_possessive_determiner()
642    }
643
644    /// Checks if the word is definitely a nominal and more specifically is labeled as __not__ (a) singular.
645    pub fn is_non_singular_nominal(&self) -> bool {
646        self.is_non_singular_noun() || self.is_non_singular_pronoun()
647    }
648
649    /// Checks if the word is definitely a nominal and more specifically is labeled as __not__ (a) plural.
650    pub fn is_non_plural_nominal(&self) -> bool {
651        self.is_non_plural_noun() || self.is_non_plural_pronoun()
652    }
653
654    // Adjective metadata queries
655
656    pub fn get_degree(&self) -> Option<Degree> {
657        self.adjective.as_ref().and_then(|a| a.degree)
658    }
659
660    pub fn is_comparative_adjective(&self) -> bool {
661        matches!(
662            self.adjective,
663            Some(AdjectiveData {
664                degree: Some(Degree::Comparative)
665            })
666        )
667    }
668
669    pub fn is_superlative_adjective(&self) -> bool {
670        matches!(
671            self.adjective,
672            Some(AdjectiveData {
673                degree: Some(Degree::Superlative)
674            })
675        )
676    }
677
678    // Degree::Positive is the default if degree is not marked in the dictionary.
679    pub fn is_positive_adjective(&self) -> bool {
680        match self.adjective {
681            Some(AdjectiveData {
682                degree: Some(Degree::Positive),
683            }) => true,
684            Some(AdjectiveData { degree: None }) => true,
685            Some(AdjectiveData {
686                degree: Some(degree),
687            }) => !matches!(degree, Degree::Comparative | Degree::Superlative),
688            _ => false,
689        }
690    }
691
692    // Determiner metadata queries
693
694    // Checks if the word is definitely a determiner and more specifically is labeled as (a) quantifier.
695    pub fn is_quantifier(&self) -> bool {
696        self.is_quantifier_determiner()
697    }
698
699    // Non-POS queries
700
701    /// Checks whether a word is _definitely_ a swear.
702    pub fn is_swear(&self) -> bool {
703        matches!(self.swear, Some(true))
704    }
705
706    /// Abbreviation is orthogonal to POS
707    pub fn is_abbreviation(&self) -> bool {
708        matches!(self.abbreviation, Some(true))
709    }
710
711    // Orthographic queries
712
713    /// Does the metadata for this word cover an all-lowercase variant? (e.g., "hello")
714    ///
715    /// This returns true if all letters in the word are lowercase. Words containing
716    /// non-letter characters (like numbers or symbols) are only considered if all
717    /// letter characters are lowercase.
718    pub fn is_lowercase(&self) -> bool {
719        self.orth_info.contains(OrthFlags::LOWERCASE)
720    }
721    /// Does the metadata for this word cover a titlecase variant? (e.g., "Hello")
722    ///
723    /// This returns true if the word is in titlecase form, which means:
724    /// - The first letter is uppercase
725    /// - All other letters are lowercase
726    /// - The word is at least 2 characters long
727    ///
728    /// Examples: "Hello", "World"
729    ///
730    /// Note: Words with internal capital letters (like "McDonald") or apostrophes (like "O'Reilly")
731    /// are not considered titlecase - they are classified as UPPER_CAMEL instead.
732    pub fn is_titlecase(&self) -> bool {
733        self.orth_info.contains(OrthFlags::TITLECASE)
734    }
735    /// Does the metadata for this word cover an all-uppercase variant? (e.g., "HELLO")
736    ///
737    /// This returns true if all letters in the word are uppercase. Words containing
738    /// non-letter characters (like numbers or symbols) are only considered if all
739    /// letter characters are uppercase.
740    ///
741    /// Examples: "HELLO", "NASA", "I"
742    pub fn is_allcaps(&self) -> bool {
743        self.orth_info.contains(OrthFlags::ALLCAPS)
744    }
745    /// Does the metadata for this word cover a lower camel case variant? (e.g., "helloWorld")
746    ///
747    /// This returns true if the word is in lower camel case, which means:
748    /// - The first letter is lowercase
749    /// - There is at least one uppercase letter after the first character
750    /// - The word must be at least 2 characters long
751    ///
752    /// Examples: "helloWorld", "getHTTPResponse", "eBay"
753    ///
754    /// Note: Single words that are all lowercase will return false.
755    /// Words starting with an uppercase letter will return false (those would be UpperCamel).
756    pub fn is_lower_camel(&self) -> bool {
757        self.orth_info.contains(OrthFlags::LOWER_CAMEL)
758    }
759    /// Does the metadata for this word cover an upper camel case / pascal case variant? (e.g., "HelloWorld")
760    ///
761    /// This returns true if the word is in upper camel case (also known as Pascal case), which means:
762    /// - The first letter is uppercase
763    /// - There is at least one other uppercase letter after the first character
764    /// - There is at least one lowercase letter after the first uppercase letter
765    /// - The word must be at least 3 characters long
766    ///
767    /// Examples:
768    /// - "HelloWorld" (standard Pascal case)
769    /// - "McDonald" (name with internal caps)
770    /// - "O'Reilly" (name with apostrophe and internal caps)
771    /// - "HttpRequest" (initialism followed by word)
772    ///
773    /// Note: Single words that are titlecase (like "Hello") will return false.
774    /// Words that are all uppercase (like "NASA") will also return false.
775    pub fn is_upper_camel(&self) -> bool {
776        self.orth_info.contains(OrthFlags::UPPER_CAMEL)
777    }
778
779    /// Does the metadata for this word cover an apostrophized variant? (e.g., "doesn't")
780    pub fn is_apostrophized(&self) -> bool {
781        self.orth_info.contains(OrthFlags::APOSTROPHE)
782    }
783
784    pub fn is_roman_numerals(&self) -> bool {
785        self.orth_info.contains(OrthFlags::ROMAN_NUMERALS)
786    }
787
788    /// Same thing as [`Self::or`], except in-place rather than a clone.
789    pub fn merge(&mut self, other: &Self) -> &mut Self {
790        macro_rules! merge {
791            ($a:expr, $b:expr) => {
792                match ($a, $b) {
793                    (Some(a), Some(b)) => Some(a.or(&b)),
794                    (Some(a), None) => Some(a),
795                    (None, Some(b)) => Some(b),
796                    (None, None) => None,
797                }
798            };
799        }
800
801        self.noun = merge!(self.noun, other.noun);
802        self.pronoun = merge!(self.pronoun, other.pronoun);
803        self.verb = merge!(self.verb, other.verb);
804        self.adjective = merge!(self.adjective, other.adjective);
805        self.adverb = merge!(self.adverb, other.adverb);
806        self.conjunction = merge!(self.conjunction, other.conjunction);
807        self.determiner = merge!(self.determiner, other.determiner);
808        self.affix = merge!(self.affix, other.affix);
809        self.preposition |= other.preposition;
810        self.dialects |= other.dialects;
811        self.orth_info |= other.orth_info;
812        self.swear = self.swear.or(other.swear);
813        self.abbreviation = self.abbreviation.or(other.abbreviation);
814        self.common |= other.common;
815        self.derived_from = self.derived_from.or(other.derived_from);
816        self.pos_tag = self.pos_tag.or(other.pos_tag);
817        self.np_member = self.np_member.or(other.np_member);
818
819        self
820    }
821}
822
823// These verb forms are morphological variations, distinct from TAM (Tense-Aspect-Mood)
824// Each form can be used in various TAM combinations:
825// - Lemma form (infinitive, citation form, dictionary form)
826//   Used in infinitives (e.g., "to sleep"), imperatives (e.g., "sleep!"), and with modals (e.g., "will sleep")
827// - Past form (past participle and simple past)
828//   Used as verbs (e.g., "slept") or adjectives (e.g., "closed door")
829// - Progressive form (present participle and gerund)
830//   Used as verbs (e.g., "sleeping"), nouns (e.g., "sleeping is important"), or adjectives (e.g., "sleeping dog")
831// - Third person singular present (-s/-es)
832//   Used for third person singular subjects (e.g., "he sleeps", "she reads")
833//
834// Important notes:
835// 1. English expresses time through auxiliary verbs, not verb form alone
836// 2. Irregular verbs can have different forms for past participle and simple past
837// 3. Future is always expressed through auxiliary verbs (e.g., "will sleep", "going to sleep")
838#[repr(u32)]
839pub enum VerbForm {
840    /// The uninflected verb form: "walk", "eat"
841    LemmaForm = 1 << 0,
842    /// The past form for regular verbs: "walked"
843    PastForm = 1 << 1,
844    /// The simple past/preterite form for irregular verbs: "ate"
845    SimplePastForm = 1 << 2,
846    /// The past participle form for irregular verbs: "eaten"
847    PastParticipleForm = 1 << 3,
848    /// The progressive/continuous/gerund/present participle form: "walking", "eating"
849    ProgressiveForm = 1 << 4,
850    /// The third person singular present form: "walks", "eats"
851    ThirdPersonSingularPresentForm = 1 << 5,
852}
853
854/// The underlying type used for verb form flags.
855pub type VerbFormFlagsUnderlyingType = u32;
856
857bitflags::bitflags! {
858    /// A collection of bit flags used to represent verb forms.
859    ///
860    /// This allows a word to be tagged with multiple verb forms when applicable.
861    #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
862    #[serde(transparent)]
863    pub struct VerbFormFlags: VerbFormFlagsUnderlyingType {
864        const LEMMA = VerbForm::LemmaForm as VerbFormFlagsUnderlyingType;
865        const PAST = VerbForm::PastForm as VerbFormFlagsUnderlyingType;
866        const PRETERITE = VerbForm::SimplePastForm as VerbFormFlagsUnderlyingType;
867        const PAST_PARTICIPLE = VerbForm::PastParticipleForm as VerbFormFlagsUnderlyingType;
868        const PROGRESSIVE = VerbForm::ProgressiveForm as VerbFormFlagsUnderlyingType;
869        const THIRD_PERSON_SINGULAR = VerbForm::ThirdPersonSingularPresentForm as VerbFormFlagsUnderlyingType;
870    }
871}
872
873#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
874pub struct VerbData {
875    pub is_linking: Option<bool>,
876    pub is_auxiliary: Option<bool>,
877    #[serde(rename = "verb_form", default)]
878    pub verb_forms: Option<VerbFormFlags>,
879}
880
881impl VerbData {
882    /// Produce a copy of `self` with the known properties of `other` set.
883    pub fn or(&self, other: &Self) -> Self {
884        let verb_forms = match (self.verb_forms, other.verb_forms) {
885            (Some(self_verb_forms), Some(other_verb_forms)) => {
886                Some(self_verb_forms | other_verb_forms)
887            }
888            (Some(self_verb_forms), None) => Some(self_verb_forms),
889            (None, Some(other_verb_forms)) => Some(other_verb_forms),
890            (None, None) => None,
891        };
892
893        Self {
894            is_linking: self.is_linking.or(other.is_linking),
895            is_auxiliary: self.is_auxiliary.or(other.is_auxiliary),
896            verb_forms,
897        }
898    }
899}
900
901// nouns can be both singular and plural: "aircraft", "biceps", "fish", "sheep"
902// TODO other noun properties may be worth adding: abstract
903#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
904pub struct NounData {
905    pub is_proper: Option<bool>,
906    pub is_singular: Option<bool>,
907    pub is_plural: Option<bool>,
908    pub is_countable: Option<bool>,
909    pub is_mass: Option<bool>,
910    pub is_possessive: Option<bool>,
911}
912
913impl NounData {
914    /// Produce a copy of `self` with the known properties of `other` set.
915    pub fn or(&self, other: &Self) -> Self {
916        Self {
917            is_proper: self.is_proper.or(other.is_proper),
918            is_singular: self.is_singular.or(other.is_singular),
919            is_plural: self.is_plural.or(other.is_plural),
920            is_countable: self.is_countable.or(other.is_countable),
921            is_mass: self.is_mass.or(other.is_mass),
922            is_possessive: self.is_possessive.or(other.is_possessive),
923        }
924    }
925}
926
927// Person is a property of pronouns; the verb 'be', plus all verbs reflect 3rd person singular with -s
928#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Is, Hash)]
929pub enum Person {
930    First,
931    Second,
932    Third,
933}
934
935// TODO for now focused on personal pronouns?
936#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
937pub struct PronounData {
938    pub is_personal: Option<bool>,
939    pub is_singular: Option<bool>,
940    pub is_plural: Option<bool>,
941    pub is_possessive: Option<bool>,
942    pub is_reflexive: Option<bool>,
943    pub person: Option<Person>,
944    pub is_subject: Option<bool>,
945    pub is_object: Option<bool>,
946}
947
948impl PronounData {
949    /// Produce a copy of `self` with the known properties of `other` set.
950    pub fn or(&self, other: &Self) -> Self {
951        Self {
952            is_personal: self.is_personal.or(other.is_personal),
953            is_singular: self.is_singular.or(other.is_singular),
954            is_plural: self.is_plural.or(other.is_plural),
955            is_possessive: self.is_possessive.or(other.is_possessive),
956            is_reflexive: self.is_reflexive.or(other.is_reflexive),
957            person: self.person.or(other.person),
958            is_subject: self.is_subject.or(other.is_subject),
959            is_object: self.is_object.or(other.is_object),
960        }
961    }
962}
963
964/// Additional metadata for determiners
965#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
966pub struct DeterminerData {
967    pub is_demonstrative: Option<bool>,
968    pub is_possessive: Option<bool>,
969    pub is_quantifier: Option<bool>,
970}
971
972impl DeterminerData {
973    /// Produce a copy of `self` with the known properties of `other` set.
974    pub fn or(&self, other: &Self) -> Self {
975        Self {
976            is_demonstrative: self.is_demonstrative.or(other.is_demonstrative),
977            is_possessive: self.is_possessive.or(other.is_possessive),
978            is_quantifier: self.is_quantifier.or(other.is_quantifier),
979        }
980    }
981}
982
983/// Degree is a property of adjectives: positive is not inflected
984/// Comparative is inflected with -er or comes after the word "more"
985/// Superlative is inflected with -est or comes after the word "most"
986#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Is, Hash)]
987pub enum Degree {
988    Positive,
989    Comparative,
990    Superlative,
991}
992
993/// Some adjectives are not comparable so don't have -er or -est forms and can't be used with "more" or "most".
994/// Some adjectives can only be used "attributively" (before a noun); some only predicatively (after "is" etc.).
995/// In old grammars words like the articles and determiners are classified as adjectives but behave differently.
996#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
997pub struct AdjectiveData {
998    pub degree: Option<Degree>,
999}
1000
1001impl AdjectiveData {
1002    /// Produce a copy of `self` with the known properties of `other` set.
1003    pub fn or(&self, other: &Self) -> Self {
1004        Self {
1005            degree: self.degree.or(other.degree),
1006        }
1007    }
1008}
1009
1010/// Adverb can be a "junk drawer" category for words which don't fit the other major categories.
1011/// The typical adverbs are "adverbs of manner", those derived from adjectives in -ly
1012/// other adverbs (time, place, etc) should probably not be considered adverbs for Harper's purposes
1013#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
1014pub struct AdverbData {
1015    pub is_manner: Option<bool>,
1016    pub is_frequency: Option<bool>,
1017    pub is_degree: Option<bool>,
1018}
1019
1020impl AdverbData {
1021    /// Produce a copy of `self` with the known properties of `other` set.
1022    pub fn or(&self, _other: &Self) -> Self {
1023        Self {
1024            is_manner: self.is_manner.or(_other.is_manner),
1025            is_frequency: self.is_frequency.or(_other.is_frequency),
1026            is_degree: self.is_degree.or(_other.is_degree),
1027        }
1028    }
1029}
1030
1031#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
1032pub struct ConjunctionData {}
1033
1034impl ConjunctionData {
1035    /// Produce a copy of `self` with the known properties of `other` set.
1036    pub fn or(&self, _other: &Self) -> Self {
1037        Self {}
1038    }
1039}
1040
1041#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Default)]
1042pub struct AffixData {
1043    pub is_prefix: Option<bool>,
1044    pub is_suffix: Option<bool>,
1045}
1046
1047impl AffixData {
1048    /// Produce a copy of `self` with the known properties of `other` set.
1049    pub fn or(&self, _other: &Self) -> Self {
1050        Self {
1051            is_prefix: self.is_prefix.or(_other.is_prefix),
1052            is_suffix: self.is_suffix.or(_other.is_suffix),
1053        }
1054    }
1055}
1056
1057/// A regional dialect.
1058///
1059/// Note: these have bit-shifted values so that they can ergonomically integrate with
1060/// `DialectFlags`. Each value here must have a unique bit index inside
1061/// `DialectsUnderlyingType`.
1062#[derive(
1063    Debug,
1064    Clone,
1065    Copy,
1066    Serialize,
1067    Deserialize,
1068    PartialEq,
1069    PartialOrd,
1070    Eq,
1071    Hash,
1072    EnumCount,
1073    EnumString,
1074    EnumIter,
1075    Display,
1076    VariantArray,
1077)]
1078pub enum Dialect {
1079    American = 1 << 0,
1080    Canadian = 1 << 1,
1081    Australian = 1 << 2,
1082    British = 1 << 3,
1083    Indian = 1 << 4,
1084}
1085impl Dialect {
1086    /// Tries to guess the dialect used in the document by finding which dialect is used the most.
1087    /// Returns `None` if it fails to find a single dialect that is used the most.
1088    #[must_use]
1089    pub fn try_guess_from_document(document: &Document) -> Option<Self> {
1090        Self::try_from(DialectFlags::get_most_used_dialects_from_document(document)).ok()
1091    }
1092
1093    /// Tries to get a dialect from its abbreviation. Returns `None` if the abbreviation is not
1094    /// recognized.
1095    ///
1096    /// # Examples
1097    ///
1098    /// ```
1099    /// use harper_core::Dialect;
1100    ///
1101    /// let abbrs = ["US", "CA", "AU", "GB", "IN"];
1102    /// let mut dialects = abbrs.iter().map(|abbr| Dialect::try_from_abbr(abbr));
1103    ///
1104    /// assert_eq!(Some(Dialect::American), dialects.next().unwrap()); // US
1105    /// assert_eq!(Some(Dialect::Canadian), dialects.next().unwrap()); // CA
1106    /// assert_eq!(Some(Dialect::Australian), dialects.next().unwrap()); // AU
1107    /// assert_eq!(Some(Dialect::British), dialects.next().unwrap()); // GB
1108    /// assert_eq!(Some(Dialect::Indian), dialects.next().unwrap()); // IN
1109    /// ```
1110    #[must_use]
1111    pub fn try_from_abbr(abbr: &str) -> Option<Self> {
1112        match abbr {
1113            "US" => Some(Self::American),
1114            "CA" => Some(Self::Canadian),
1115            "AU" => Some(Self::Australian),
1116            "GB" => Some(Self::British),
1117            "IN" => Some(Self::Indian),
1118            _ => None,
1119        }
1120    }
1121    // BCP-47 https://www.rfc-editor.org/rfc/rfc5646
1122    pub fn try_from_bcp47(bcp47: &str) -> Option<Self> {
1123        bcp47.strip_prefix("en-").and_then(Self::try_from_abbr)
1124    }
1125}
1126impl TryFrom<DialectFlags> for Dialect {
1127    type Error = ();
1128
1129    /// Attempts to convert `DialectFlags` to a single `Dialect`.
1130    ///
1131    /// # Errors
1132    ///
1133    /// Will return `Err` if more than one dialect is enabled or if an undefined dialect is
1134    /// enabled.
1135    fn try_from(dialect_flags: DialectFlags) -> Result<Self, Self::Error> {
1136        // Ensure only one dialect is enabled before converting.
1137        if dialect_flags.bits().count_ones() == 1 {
1138            match dialect_flags {
1139                df if df.is_dialect_enabled_strict(Dialect::American) => Ok(Dialect::American),
1140                df if df.is_dialect_enabled_strict(Dialect::Canadian) => Ok(Dialect::Canadian),
1141                df if df.is_dialect_enabled_strict(Dialect::Australian) => Ok(Dialect::Australian),
1142                df if df.is_dialect_enabled_strict(Dialect::British) => Ok(Dialect::British),
1143                df if df.is_dialect_enabled_strict(Dialect::Indian) => Ok(Dialect::Indian),
1144                _ => Err(()),
1145            }
1146        } else {
1147            // More than one dialect enabled; can't soundly convert.
1148            Err(())
1149        }
1150    }
1151}
1152
1153// The underlying type used for DialectFlags.
1154// At the time of writing, this is currently a `u8`. If we want to define more than 8 dialects in
1155// the future, we will need to switch this to a larger type.
1156type DialectFlagsUnderlyingType = u8;
1157
1158bitflags::bitflags! {
1159    /// A collection of bit flags used to represent enabled dialects.
1160    ///
1161    /// This is generally used to allow a word (or similar) to be tagged with multiple dialects.
1162    #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash)]
1163    #[serde(transparent)]
1164    pub struct DialectFlags: DialectFlagsUnderlyingType {
1165        const AMERICAN = Dialect::American as DialectFlagsUnderlyingType;
1166        const CANADIAN = Dialect::Canadian as DialectFlagsUnderlyingType;
1167        const AUSTRALIAN = Dialect::Australian as DialectFlagsUnderlyingType;
1168        const BRITISH = Dialect::British as DialectFlagsUnderlyingType;
1169        const INDIAN = Dialect::Indian as DialectFlagsUnderlyingType;
1170    }
1171}
1172impl DialectFlags {
1173    /// Checks if the provided dialect is enabled.
1174    /// If no dialect is explicitly enabled, it is assumed that all dialects are enabled.
1175    #[must_use]
1176    pub fn is_dialect_enabled(self, dialect: Dialect) -> bool {
1177        self.is_empty() || self.intersects(Self::from_dialect(dialect))
1178    }
1179
1180    /// Checks if the provided dialect is ***explicitly*** enabled.
1181    ///
1182    /// Unlike `is_dialect_enabled`, this will return false when no dialects are explicitly
1183    /// enabled.
1184    #[must_use]
1185    pub fn is_dialect_enabled_strict(self, dialect: Dialect) -> bool {
1186        self.intersects(Self::from_dialect(dialect))
1187    }
1188
1189    /// Constructs a `DialectFlags` from the provided `Dialect`, with only that dialect being
1190    /// enabled.
1191    ///
1192    /// # Panics
1193    ///
1194    /// This will panic if `dialect` represents a dialect that is not defined in
1195    /// `DialectFlags`.
1196    #[must_use]
1197    pub fn from_dialect(dialect: Dialect) -> Self {
1198        let Some(out) = Self::from_bits(dialect as DialectFlagsUnderlyingType) else {
1199            panic!("The '{dialect}' dialect isn't defined in DialectFlags!");
1200        };
1201        out
1202    }
1203
1204    /// Gets the most commonly used dialect(s) in the document.
1205    ///
1206    /// If multiple dialects are used equally often, they will all be enabled in the returned
1207    /// `DialectFlags`. On the other hand, if there is a single dialect that is used the most, it
1208    /// will be the only one enabled.
1209    #[must_use]
1210    pub fn get_most_used_dialects_from_document(document: &Document) -> Self {
1211        // Initialize counters.
1212        let mut dialect_counters: [(Dialect, usize); Dialect::COUNT] = Dialect::VARIANTS
1213            .iter()
1214            .map(|d| (*d, 0))
1215            .collect_array()
1216            .unwrap();
1217
1218        // Count word dialects.
1219        document.iter_words().for_each(|w| {
1220            if let TokenKind::Word(Some(lexeme_metadata)) = &w.kind {
1221                // If the token is a word, iterate though the dialects in `dialect_counters` and
1222                // increment those counters where the word has the respective dialect enabled.
1223                dialect_counters.iter_mut().for_each(|(dialect, count)| {
1224                    if lexeme_metadata.dialects.is_dialect_enabled(*dialect) {
1225                        *count += 1;
1226                    }
1227                });
1228            }
1229        });
1230
1231        // Find max counter.
1232        let max_counter = dialect_counters
1233            .iter()
1234            .map(|(_, count)| count)
1235            .max()
1236            .unwrap();
1237        // Get and convert the collection of most used dialects into a `DialectFlags`.
1238        dialect_counters
1239            .into_iter()
1240            .filter(|(_, count)| count == max_counter)
1241            .fold(DialectFlags::empty(), |acc, dialect| {
1242                // Fold most used dialects into `DialectFlags` via bitwise or.
1243                acc | Self::from_dialect(dialect.0)
1244            })
1245    }
1246}
1247impl Default for DialectFlags {
1248    /// A default value with no dialects explicitly enabled.
1249    /// Implicitly, this state corresponds to all dialects being enabled.
1250    fn default() -> Self {
1251        Self::empty()
1252    }
1253}
1254
1255#[cfg(test)]
1256pub mod tests {
1257    use crate::DictWordMetadata;
1258    use crate::spell::{Dictionary, FstDictionary};
1259
1260    // Helper function to get metadata from the curated dictionary
1261    pub fn md(word: &str) -> DictWordMetadata {
1262        FstDictionary::curated()
1263            .get_word_metadata_str(word)
1264            .unwrap_or_else(|| panic!("Word '{word}' not found in dictionary"))
1265            .into_owned()
1266    }
1267
1268    mod dialect {
1269        use super::super::{Dialect, DialectFlags};
1270        use crate::Document;
1271
1272        #[test]
1273        fn guess_british_dialect() {
1274            let document = Document::new_plain_english_curated("Aluminium was used.");
1275            let df = DialectFlags::get_most_used_dialects_from_document(&document);
1276            assert!(
1277                df.is_dialect_enabled_strict(Dialect::British)
1278                    && !df.is_dialect_enabled_strict(Dialect::American)
1279            );
1280        }
1281
1282        #[test]
1283        fn guess_american_dialect() {
1284            let document = Document::new_plain_english_curated("Aluminum was used.");
1285            let df = DialectFlags::get_most_used_dialects_from_document(&document);
1286            assert!(
1287                df.is_dialect_enabled_strict(Dialect::American)
1288                    && !df.is_dialect_enabled_strict(Dialect::British)
1289            );
1290        }
1291    }
1292
1293    mod noun {
1294        use crate::dict_word_metadata::tests::md;
1295
1296        #[test]
1297        fn puppy_is_noun() {
1298            assert!(md("puppy").is_noun());
1299        }
1300
1301        #[test]
1302        fn prepare_is_not_noun() {
1303            assert!(!md("prepare").is_noun());
1304        }
1305
1306        #[test]
1307        fn paris_is_proper_noun() {
1308            assert!(md("Paris").is_proper_noun());
1309        }
1310
1311        #[test]
1312        fn permit_is_non_proper_noun() {
1313            assert!(md("lapdog").is_non_proper_noun());
1314        }
1315
1316        #[test]
1317        fn hound_is_singular_noun() {
1318            assert!(md("hound").is_singular_noun());
1319        }
1320
1321        #[test]
1322        fn pooches_is_non_singular_noun() {
1323            assert!(md("pooches").is_non_singular_noun());
1324        }
1325
1326        // Make sure is_non_xxx_noun methods don't behave like is_not_xxx_noun.
1327        // In other words, make sure they don't return true for words that are not nouns.
1328        // They must only pass for words that are nouns but not singular etc.
1329        #[test]
1330        fn loyal_doesnt_pass_is_non_singular_noun() {
1331            assert!(!md("loyal").is_non_singular_noun());
1332        }
1333
1334        #[test]
1335        fn hounds_is_plural_noun() {
1336            assert!(md("hounds").is_plural_noun());
1337        }
1338
1339        #[test]
1340        fn pooch_is_non_plural_noun() {
1341            assert!(md("pooch").is_non_plural_noun());
1342        }
1343
1344        #[test]
1345        fn fish_is_singular_noun() {
1346            assert!(md("fish").is_singular_noun());
1347        }
1348
1349        #[test]
1350        fn fish_is_plural_noun() {
1351            assert!(md("fish").is_plural_noun());
1352        }
1353
1354        #[test]
1355        fn fishes_is_plural_noun() {
1356            assert!(md("fishes").is_plural_noun());
1357        }
1358
1359        #[test]
1360        fn sheep_is_singular_noun() {
1361            assert!(md("sheep").is_singular_noun());
1362        }
1363
1364        #[test]
1365        fn sheep_is_plural_noun() {
1366            assert!(md("sheep").is_plural_noun());
1367        }
1368
1369        #[test]
1370        #[should_panic]
1371        fn sheeps_is_not_word() {
1372            md("sheeps");
1373        }
1374
1375        #[test]
1376        fn bicep_is_singular_noun() {
1377            assert!(md("bicep").is_singular_noun());
1378        }
1379
1380        #[test]
1381        fn biceps_is_singular_noun() {
1382            assert!(md("biceps").is_singular_noun());
1383        }
1384
1385        #[test]
1386        fn biceps_is_plural_noun() {
1387            assert!(md("biceps").is_plural_noun());
1388        }
1389
1390        #[test]
1391        fn aircraft_is_singular_noun() {
1392            assert!(md("aircraft").is_singular_noun());
1393        }
1394
1395        #[test]
1396        fn aircraft_is_plural_noun() {
1397            assert!(md("aircraft").is_plural_noun());
1398        }
1399
1400        #[test]
1401        #[should_panic]
1402        fn aircrafts_is_not_word() {
1403            md("aircrafts");
1404        }
1405
1406        #[test]
1407        fn dog_apostrophe_s_is_possessive_noun() {
1408            assert!(md("dog's").is_possessive_noun());
1409        }
1410
1411        #[test]
1412        fn dogs_is_non_possessive_noun() {
1413            assert!(md("dogs").is_non_possessive_noun());
1414        }
1415
1416        // noun countability
1417
1418        #[test]
1419        fn dog_is_countable() {
1420            assert!(md("dog").is_countable_noun());
1421        }
1422        #[test]
1423        fn dog_is_non_mass_noun() {
1424            assert!(md("dog").is_non_mass_noun());
1425        }
1426
1427        #[test]
1428        fn furniture_is_mass_noun() {
1429            assert!(md("furniture").is_mass_noun());
1430        }
1431        #[test]
1432        fn furniture_is_non_countable_noun() {
1433            assert!(md("furniture").is_non_countable_noun());
1434        }
1435
1436        #[test]
1437        fn equipment_is_mass_noun() {
1438            assert!(md("equipment").is_mass_noun());
1439        }
1440        #[test]
1441        fn equipment_is_non_countable_noun() {
1442            assert!(md("equipment").is_non_countable_noun());
1443        }
1444
1445        #[test]
1446        fn beer_is_countable_noun() {
1447            assert!(md("beer").is_countable_noun());
1448        }
1449        #[test]
1450        fn beer_is_mass_noun() {
1451            assert!(md("beer").is_mass_noun());
1452        }
1453    }
1454
1455    mod pronoun {
1456        use crate::dict_word_metadata::tests::md;
1457
1458        mod i_me_myself {
1459            use crate::dict_word_metadata::tests::md;
1460
1461            #[test]
1462            fn i_is_pronoun() {
1463                assert!(md("I").is_pronoun());
1464            }
1465            #[test]
1466            fn i_is_personal_pronoun() {
1467                assert!(md("I").is_personal_pronoun());
1468            }
1469            #[test]
1470            fn i_is_singular_pronoun() {
1471                assert!(md("I").is_singular_pronoun());
1472            }
1473            #[test]
1474            fn i_is_subject_pronoun() {
1475                assert!(md("I").is_subject_pronoun());
1476            }
1477
1478            #[test]
1479            fn me_is_pronoun() {
1480                assert!(md("me").is_pronoun());
1481            }
1482            #[test]
1483            fn me_is_personal_pronoun() {
1484                assert!(md("me").is_personal_pronoun());
1485            }
1486            #[test]
1487            fn me_is_singular_pronoun() {
1488                assert!(md("me").is_singular_pronoun());
1489            }
1490            #[test]
1491            fn me_is_object_pronoun() {
1492                assert!(md("me").is_object_pronoun());
1493            }
1494
1495            #[test]
1496            fn myself_is_pronoun() {
1497                assert!(md("myself").is_pronoun());
1498            }
1499            #[test]
1500            fn myself_is_personal_pronoun() {
1501                assert!(md("myself").is_personal_pronoun());
1502            }
1503            #[test]
1504            fn myself_is_singular_pronoun() {
1505                assert!(md("myself").is_singular_pronoun());
1506            }
1507            #[test]
1508            fn myself_is_reflexive_pronoun() {
1509                assert!(md("myself").is_reflexive_pronoun());
1510            }
1511        }
1512
1513        mod we_us_ourselves {
1514            use crate::dict_word_metadata::tests::md;
1515
1516            #[test]
1517            fn we_is_pronoun() {
1518                assert!(md("we").is_pronoun());
1519            }
1520            #[test]
1521            fn we_is_personal_pronoun() {
1522                assert!(md("we").is_personal_pronoun());
1523            }
1524            #[test]
1525            fn we_is_plural_pronoun() {
1526                assert!(md("we").is_plural_pronoun());
1527            }
1528            #[test]
1529            fn we_is_subject_pronoun() {
1530                assert!(md("we").is_subject_pronoun());
1531            }
1532
1533            #[test]
1534            fn us_is_pronoun() {
1535                assert!(md("us").is_pronoun());
1536            }
1537            #[test]
1538            fn us_is_personal_pronoun() {
1539                assert!(md("us").is_personal_pronoun());
1540            }
1541            #[test]
1542            fn us_is_plural_pronoun() {
1543                assert!(md("us").is_plural_pronoun());
1544            }
1545            #[test]
1546            fn us_is_object_pronoun() {
1547                assert!(md("us").is_object_pronoun());
1548            }
1549
1550            #[test]
1551            fn ourselves_is_pronoun() {
1552                assert!(md("ourselves").is_pronoun());
1553            }
1554            #[test]
1555            fn ourselves_is_personal_pronoun() {
1556                assert!(md("ourselves").is_personal_pronoun());
1557            }
1558            #[test]
1559            fn ourselves_is_plural_pronoun() {
1560                assert!(md("ourselves").is_plural_pronoun());
1561            }
1562            #[test]
1563            fn ourselves_is_reflexive_pronoun() {
1564                assert!(md("ourselves").is_reflexive_pronoun());
1565            }
1566        }
1567
1568        mod you_yourself {
1569            use crate::dict_word_metadata::tests::md;
1570
1571            #[test]
1572            fn you_is_pronoun() {
1573                assert!(md("you").is_pronoun());
1574            }
1575            #[test]
1576            fn you_is_personal_pronoun() {
1577                assert!(md("you").is_personal_pronoun());
1578            }
1579            #[test]
1580            fn you_is_singular_pronoun() {
1581                assert!(md("you").is_singular_pronoun());
1582            }
1583            #[test]
1584            fn you_is_plural_pronoun() {
1585                assert!(md("you").is_plural_pronoun());
1586            }
1587            #[test]
1588            fn you_is_subject_pronoun() {
1589                assert!(md("you").is_subject_pronoun());
1590            }
1591            #[test]
1592            fn you_is_object_pronoun() {
1593                assert!(md("you").is_object_pronoun());
1594            }
1595            #[test]
1596            fn yourself_is_pronoun() {
1597                assert!(md("yourself").is_pronoun());
1598            }
1599            #[test]
1600            fn yourself_is_personal_pronoun() {
1601                assert!(md("yourself").is_personal_pronoun());
1602            }
1603            #[test]
1604            fn yourself_is_singular_pronoun() {
1605                assert!(md("yourself").is_singular_pronoun());
1606            }
1607            #[test]
1608            fn yourself_is_reflexive_pronoun() {
1609                assert!(md("yourself").is_reflexive_pronoun());
1610            }
1611        }
1612
1613        mod he_him_himself {
1614            use crate::dict_word_metadata::tests::md;
1615
1616            #[test]
1617            fn he_is_pronoun() {
1618                assert!(md("he").is_pronoun());
1619            }
1620            #[test]
1621            fn he_is_personal_pronoun() {
1622                assert!(md("he").is_personal_pronoun());
1623            }
1624            #[test]
1625            fn he_is_singular_pronoun() {
1626                assert!(md("he").is_singular_pronoun());
1627            }
1628            #[test]
1629            fn he_is_subject_pronoun() {
1630                assert!(md("he").is_subject_pronoun());
1631            }
1632
1633            #[test]
1634            fn him_is_pronoun() {
1635                assert!(md("him").is_pronoun());
1636            }
1637            #[test]
1638            fn him_is_personal_pronoun() {
1639                assert!(md("him").is_personal_pronoun());
1640            }
1641            #[test]
1642            fn him_is_singular_pronoun() {
1643                assert!(md("him").is_singular_pronoun());
1644            }
1645            #[test]
1646            fn him_is_object_pronoun() {
1647                assert!(md("him").is_object_pronoun());
1648            }
1649
1650            #[test]
1651            fn himself_is_pronoun() {
1652                assert!(md("himself").is_pronoun());
1653            }
1654            #[test]
1655            fn himself_is_personal_pronoun() {
1656                assert!(md("himself").is_personal_pronoun());
1657            }
1658            #[test]
1659            fn himself_is_singular_pronoun() {
1660                assert!(md("himself").is_singular_pronoun());
1661            }
1662            #[test]
1663            fn himself_is_reflexive_pronoun() {
1664                assert!(md("himself").is_reflexive_pronoun());
1665            }
1666        }
1667
1668        mod she_her_herself {
1669            use crate::dict_word_metadata::tests::md;
1670
1671            #[test]
1672            fn she_is_pronoun() {
1673                assert!(md("she").is_pronoun());
1674            }
1675            #[test]
1676            fn she_is_personal_pronoun() {
1677                assert!(md("she").is_personal_pronoun());
1678            }
1679            #[test]
1680            fn she_is_singular_pronoun() {
1681                assert!(md("she").is_singular_pronoun());
1682            }
1683            #[test]
1684            fn she_is_subject_pronoun() {
1685                assert!(md("she").is_subject_pronoun());
1686            }
1687
1688            #[test]
1689            fn her_is_pronoun() {
1690                assert!(md("her").is_pronoun());
1691            }
1692            #[test]
1693            fn her_is_personal_pronoun() {
1694                assert!(md("her").is_personal_pronoun());
1695            }
1696            #[test]
1697            fn her_is_singular_pronoun() {
1698                assert!(md("her").is_singular_pronoun());
1699            }
1700            #[test]
1701            fn her_is_object_pronoun() {
1702                assert!(md("her").is_object_pronoun());
1703            }
1704
1705            #[test]
1706            fn herself_is_pronoun() {
1707                assert!(md("herself").is_pronoun());
1708            }
1709            #[test]
1710            fn herself_is_personal_pronoun() {
1711                assert!(md("herself").is_personal_pronoun());
1712            }
1713            #[test]
1714            fn herself_is_singular_pronoun() {
1715                assert!(md("herself").is_singular_pronoun());
1716            }
1717            #[test]
1718            fn herself_is_reflexive_pronoun() {
1719                assert!(md("herself").is_reflexive_pronoun());
1720            }
1721        }
1722
1723        mod it_itself {
1724            use crate::dict_word_metadata::tests::md;
1725
1726            #[test]
1727            fn it_is_pronoun() {
1728                assert!(md("it").is_pronoun());
1729            }
1730            #[test]
1731            fn it_is_personal_pronoun() {
1732                assert!(md("it").is_personal_pronoun());
1733            }
1734            #[test]
1735            fn it_is_singular_pronoun() {
1736                assert!(md("it").is_singular_pronoun());
1737            }
1738            #[test]
1739            fn it_is_subject_pronoun() {
1740                assert!(md("it").is_subject_pronoun());
1741            }
1742            #[test]
1743            fn it_is_object_pronoun() {
1744                assert!(md("it").is_object_pronoun());
1745            }
1746
1747            #[test]
1748            fn itself_is_pronoun() {
1749                assert!(md("itself").is_pronoun());
1750            }
1751            #[test]
1752            fn itself_is_personal_pronoun() {
1753                assert!(md("itself").is_personal_pronoun());
1754            }
1755            #[test]
1756            fn itself_is_singular_pronoun() {
1757                assert!(md("itself").is_singular_pronoun());
1758            }
1759            #[test]
1760            fn itself_is_reflexive_pronoun() {
1761                assert!(md("itself").is_reflexive_pronoun());
1762            }
1763        }
1764
1765        mod they_them_themselves {
1766            use crate::dict_word_metadata::tests::md;
1767
1768            #[test]
1769            fn they_is_pronoun() {
1770                assert!(md("they").is_pronoun());
1771            }
1772            #[test]
1773            fn they_is_personal_pronoun() {
1774                assert!(md("they").is_personal_pronoun());
1775            }
1776            #[test]
1777            fn they_is_plural_pronoun() {
1778                assert!(md("they").is_plural_pronoun());
1779            }
1780            #[test]
1781            fn they_is_subject_pronoun() {
1782                assert!(md("they").is_subject_pronoun());
1783            }
1784
1785            #[test]
1786            fn them_is_pronoun() {
1787                assert!(md("them").is_pronoun());
1788            }
1789            #[test]
1790            fn them_is_personal_pronoun() {
1791                assert!(md("them").is_personal_pronoun());
1792            }
1793            #[test]
1794            fn them_is_plural_pronoun() {
1795                assert!(md("them").is_plural_pronoun());
1796            }
1797            #[test]
1798            fn them_is_object_pronoun() {
1799                assert!(md("them").is_object_pronoun());
1800            }
1801
1802            #[test]
1803            fn themselves_is_pronoun() {
1804                assert!(md("themselves").is_pronoun());
1805            }
1806            #[test]
1807            fn themselves_is_personal_pronoun() {
1808                assert!(md("themselves").is_personal_pronoun());
1809            }
1810            #[test]
1811            fn themselves_is_plural_pronoun() {
1812                assert!(md("themselves").is_plural_pronoun());
1813            }
1814            #[test]
1815            fn themselves_is_reflexive_pronoun() {
1816                assert!(md("themselves").is_reflexive_pronoun());
1817            }
1818        }
1819
1820        // Possessive pronouns (not to be confused with possessive adjectives/determiners)
1821        #[test]
1822        fn mine_is_pronoun() {
1823            assert!(md("mine").is_pronoun());
1824        }
1825        #[test]
1826        fn ours_is_pronoun() {
1827            assert!(md("ours").is_pronoun());
1828        }
1829        #[test]
1830        fn yours_is_pronoun() {
1831            assert!(md("yours").is_pronoun());
1832        }
1833        #[test]
1834        fn his_is_pronoun() {
1835            assert!(md("his").is_pronoun());
1836        }
1837        #[test]
1838        fn hers_is_pronoun() {
1839            assert!(md("hers").is_pronoun());
1840        }
1841        #[test]
1842        fn its_is_pronoun() {
1843            assert!(md("its").is_pronoun());
1844        }
1845        #[test]
1846        fn theirs_is_pronoun() {
1847            assert!(md("theirs").is_pronoun());
1848        }
1849
1850        // archaic pronouns
1851        #[test]
1852        fn archaic_pronouns() {
1853            assert!(md("thou").is_pronoun());
1854            assert!(md("thee").is_pronoun());
1855            assert!(md("thyself").is_pronoun());
1856            assert!(md("thine").is_pronoun());
1857        }
1858
1859        // generic pronouns
1860        #[test]
1861        fn generic_pronouns() {
1862            assert!(md("one").is_pronoun());
1863            assert!(md("oneself").is_pronoun());
1864        }
1865
1866        // relative and interrogative pronouns
1867        #[test]
1868        fn relative_and_interrogative_pronouns() {
1869            assert!(md("who").is_pronoun());
1870            assert!(md("whom").is_pronoun());
1871            assert!(md("whose").is_pronoun());
1872            assert!(md("which").is_pronoun());
1873            assert!(md("what").is_pronoun());
1874        }
1875
1876        // nonstandard pronouns
1877        #[test]
1878        #[ignore = "not in dictionary"]
1879        fn nonstandard_pronouns() {
1880            assert!(md("themself").pronoun.is_some());
1881            assert!(md("y'all'").pronoun.is_some());
1882        }
1883    }
1884
1885    mod nominal {
1886        use crate::dict_word_metadata::tests::md;
1887
1888        #[test]
1889        fn my_is_possessive_nominal() {
1890            assert!(md("my").is_possessive_nominal());
1891        }
1892
1893        #[test]
1894        fn mine_is_not_possessive_nominal() {
1895            assert!(!md("mine").is_possessive_nominal());
1896        }
1897
1898        #[test]
1899        fn freds_is_possessive_nominal() {
1900            assert!(md("Fred's").is_possessive_nominal());
1901        }
1902
1903        #[test]
1904        fn fred_is_not_possessive_nominal() {
1905            assert!(!md("Fred").is_possessive_nominal());
1906        }
1907
1908        #[test]
1909        fn dogs_is_possessive_nominal() {
1910            assert!(md("dog's").is_possessive_nominal());
1911        }
1912
1913        #[test]
1914        fn microsofts_is_possessive_nominal() {
1915            assert!(md("Microsoft's").is_possessive_nominal());
1916        }
1917    }
1918
1919    mod adjective {
1920        use crate::{Degree, dict_word_metadata::tests::md};
1921
1922        // Getting degrees
1923
1924        #[test]
1925        #[ignore = "not marked yet because it might not be reliable"]
1926        fn big_is_positive() {
1927            assert_eq!(md("big").get_degree(), Some(Degree::Positive));
1928        }
1929
1930        #[test]
1931        fn bigger_is_comparative() {
1932            assert_eq!(md("bigger").get_degree(), Some(Degree::Comparative));
1933        }
1934
1935        #[test]
1936        fn biggest_is_superlative() {
1937            assert_eq!(md("biggest").get_degree(), Some(Degree::Superlative));
1938        }
1939
1940        #[test]
1941        #[should_panic(expected = "Word 'bigly' not found in dictionary")]
1942        fn bigly_is_not_an_adjective_form_we_track() {
1943            assert_eq!(md("bigly").get_degree(), None);
1944        }
1945
1946        // Calling is_ methods
1947
1948        // TODO: positive degree not implemented
1949
1950        #[test]
1951        fn bigger_is_comparative_adjective() {
1952            assert!(md("bigger").is_comparative_adjective());
1953        }
1954
1955        #[test]
1956        fn biggest_is_superlative_adjective() {
1957            assert!(md("biggest").is_superlative_adjective());
1958        }
1959    }
1960
1961    #[test]
1962    fn the_is_determiner() {
1963        assert!(md("the").is_determiner());
1964    }
1965    #[test]
1966    fn this_is_demonstrative_determiner() {
1967        assert!(md("this").is_demonstrative_determiner());
1968    }
1969    #[test]
1970    fn your_is_possessive_determiner() {
1971        assert!(md("your").is_possessive_determiner());
1972    }
1973
1974    #[test]
1975    fn every_is_quantifier() {
1976        assert!(md("every").is_quantifier());
1977    }
1978
1979    #[test]
1980    fn the_isnt_quantifier() {
1981        assert!(!md("the").is_quantifier());
1982    }
1983
1984    #[test]
1985    fn equipment_is_mass_noun() {
1986        assert!(md("equipment").is_mass_noun());
1987    }
1988
1989    #[test]
1990    fn equipment_is_non_countable_noun() {
1991        assert!(md("equipment").is_non_countable_noun());
1992    }
1993
1994    #[test]
1995    fn equipment_isnt_countable_noun() {
1996        assert!(!md("equipment").is_countable_noun());
1997    }
1998
1999    #[test]
2000    fn infrastructure_is_mass_noun_only() {
2001        assert!(md("infrastructure").is_mass_noun_only());
2002    }
2003
2004    #[test]
2005    fn beer_is_not_mass_noun_only() {
2006        assert!(!md("beer").is_mass_noun_only());
2007    }
2008
2009    #[test]
2010    fn sheep_is_not_singular_only() {
2011        assert!(!md("sheep").is_singular_noun_only());
2012    }
2013
2014    #[test]
2015    fn sheep_is_not_plural_only() {
2016        assert!(!md("sheep").is_plural_noun_only());
2017    }
2018
2019    #[test]
2020    fn ox_is_singular_only() {
2021        assert!(md("ox").is_singular_noun_only());
2022    }
2023
2024    #[test]
2025    fn oxen_is_plural_only() {
2026        assert!(md("oxen").is_plural_noun_only());
2027    }
2028
2029    mod verb {
2030        use crate::dict_word_metadata::tests::md;
2031
2032        #[test]
2033        fn lemma_walk() {
2034            let md = md("walk");
2035            assert!(md.is_verb_lemma())
2036        }
2037
2038        #[test]
2039        fn lemma_fix() {
2040            let md = md("fix");
2041            assert!(md.is_verb_lemma())
2042        }
2043
2044        #[test]
2045        fn progressive_walking() {
2046            let md = md("walking");
2047            assert!(md.is_verb_progressive_form())
2048        }
2049
2050        #[test]
2051        fn past_walked() {
2052            let md = md("walked");
2053            assert!(md.is_verb_past_form())
2054        }
2055
2056        #[test]
2057        fn regular_past_thought() {
2058            let md = md("thought");
2059            assert!(md.is_verb_regular_past_form())
2060        }
2061
2062        #[test]
2063        fn simple_past_ate() {
2064            let md = md("ate");
2065            assert!(md.is_verb_simple_past_form())
2066        }
2067
2068        #[test]
2069        fn past_participle_eaten() {
2070            let md = md("eaten");
2071            assert!(md.is_verb_past_participle_form())
2072        }
2073
2074        #[test]
2075        fn ate_is_simple_past_only() {
2076            let md = md("ate");
2077            assert!(md.is_verb_simple_past_only());
2078            assert!(!md.is_verb_past_participle_only());
2079        }
2080
2081        #[test]
2082        fn eaten_is_past_participle_only() {
2083            let md = md("eaten");
2084            assert!(md.is_verb_past_participle_only());
2085            assert!(!md.is_verb_simple_past_only());
2086        }
2087
2088        #[test]
2089        fn thought_is_neither_past_form_only() {
2090            let md = md("thought");
2091            assert!(!md.is_verb_simple_past_only());
2092            assert!(!md.is_verb_past_participle_only());
2093        }
2094
2095        #[test]
2096        fn shared_past_forms_are_neither_past_form_only() {
2097            let md = md("thought");
2098            assert!(!md.is_verb_simple_past_only());
2099            assert!(!md.is_verb_past_participle_only());
2100            assert!(md.is_verb_regular_past_form());
2101        }
2102
2103        #[test]
2104        fn distinct_past_forms_are_not_regular_past() {
2105            assert!(!md("ate").is_verb_regular_past_form());
2106            assert!(!md("eaten").is_verb_regular_past_form());
2107            assert!(!md("walked").is_verb_regular_past_form());
2108        }
2109
2110        #[test]
2111        fn third_pers_sing_walks() {
2112            let md = md("walks");
2113            assert!(md.is_verb_third_person_singular_present_form())
2114        }
2115    }
2116}