harper_core/
irregular_verbs.rs

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