Skip to main content

harper_core/
irregular_verbs.rs

1use serde::Deserialize;
2use std::sync::{Arc, LazyLock};
3
4type Verb = (String, String, String);
5
6#[derive(Debug, Deserialize)]
7pub struct IrregularVerbs {
8    verbs: Vec<Verb>,
9}
10
11/// The uncached function that is used to produce the original copy of the
12/// irregular verb table.
13fn uncached_inner_new() -> Arc<IrregularVerbs> {
14    IrregularVerbs::from_json_file(include_str!("../irregular_verbs.json"))
15        .map(Arc::new)
16        .unwrap_or_else(|e| panic!("Failed to load irregular verb table: {}", e))
17}
18
19static VERBS: LazyLock<Arc<IrregularVerbs>> = LazyLock::new(uncached_inner_new);
20
21impl IrregularVerbs {
22    pub fn new() -> Self {
23        Self { verbs: vec![] }
24    }
25
26    pub fn from_json_file(json: &str) -> Result<Self, serde_json::Error> {
27        // Deserialize into Vec<serde_json::Value> to handle mixed types
28        let values: Vec<serde_json::Value> =
29            serde_json::from_str(json).expect("Failed to parse irregular verbs JSON");
30
31        let mut verbs = Vec::new();
32
33        for value in values {
34            match value {
35                serde_json::Value::Array(arr) if arr.len() == 3 => {
36                    // Handle array of 3 strings
37                    if let (Some(lemma), Some(preterite), Some(past_participle)) =
38                        (arr[0].as_str(), arr[1].as_str(), arr[2].as_str())
39                    {
40                        verbs.push((
41                            lemma.to_string(),
42                            preterite.to_string(),
43                            past_participle.to_string(),
44                        ));
45                    }
46                }
47                // Strings are used for comments to guide contributors editing the file
48                serde_json::Value::String(_) => {}
49                _ => {}
50            }
51        }
52
53        Ok(Self { verbs })
54    }
55
56    pub fn curated() -> Arc<Self> {
57        (*VERBS).clone()
58    }
59
60    pub fn get_past_participle_for_preterite(&self, preterite: &str) -> Option<&str> {
61        self.verbs
62            .iter()
63            .find(|(_, pt, _)| pt.eq_ignore_ascii_case(preterite))
64            .map(|(_, _, pp)| pp.as_str())
65    }
66
67    pub fn get_lemma_for_preterite(&self, preterite: &str) -> Option<&str> {
68        self.verbs
69            .iter()
70            .find(|(_, pt, _)| pt.eq_ignore_ascii_case(preterite))
71            .map(|(lemma, _, _)| lemma.as_str())
72    }
73
74    pub fn get_preterite_for_lemma(&self, lemma: &str) -> Option<&str> {
75        self.verbs
76            .iter()
77            .find(|(l, _, _)| l.eq_ignore_ascii_case(lemma))
78            .map(|(_, pt, _)| pt.as_str())
79    }
80
81    pub fn get_past_participle_for_lemma(&self, lemma: &str) -> Option<&str> {
82        self.verbs
83            .iter()
84            .find(|(l, _, _)| l.eq_ignore_ascii_case(lemma))
85            .map(|(_, _, pp)| pp.as_str())
86    }
87
88    pub fn get_pasts_for_lemma(&self, lemma: &str) -> Option<(&str, &str)> {
89        self.verbs
90            .iter()
91            .find(|(l, _, _)| l.eq_ignore_ascii_case(lemma))
92            .map(|(_, pt, pp)| (pt.as_str(), pp.as_str()))
93    }
94}
95
96impl Default for IrregularVerbs {
97    fn default() -> Self {
98        Self::new()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn can_find_irregular_past_participle_for_preterite_lowercase() {
108        assert_eq!(
109            IrregularVerbs::curated().get_past_participle_for_preterite("arose"),
110            Some("arisen")
111        );
112    }
113
114    #[test]
115    fn can_find_irregular_past_participle_for_preterite_uppercase() {
116        assert_eq!(
117            IrregularVerbs::curated().get_past_participle_for_preterite("WENT"),
118            Some("gone")
119        );
120    }
121
122    #[test]
123    fn can_find_irregular_past_participle_same_as_past_tense() {
124        assert_eq!(
125            IrregularVerbs::curated().get_past_participle_for_preterite("taught"),
126            Some("taught")
127        );
128    }
129
130    #[test]
131    fn cant_find_regular_past_participle() {
132        assert_eq!(
133            IrregularVerbs::curated().get_past_participle_for_preterite("walked"),
134            None
135        );
136    }
137
138    #[test]
139    fn cant_find_non_verb() {
140        assert_eq!(
141            IrregularVerbs::curated().get_past_participle_for_preterite("the"),
142            None
143        );
144    }
145}