Skip to main content

rust_lstar/
letter.rs

1use std::fmt;
2use std::hash::{Hash, Hasher};
3
4/// Represents a single letter/symbol in the alphabet.
5#[derive(Clone)]
6pub enum Letter {
7    Single(String),
8    Multiple(Vec<String>),
9}
10
11impl Letter {
12    /// Create a new letter from a single symbol
13    pub fn new<S: ToString>(symbol: S) -> Self {
14        Letter::Single(symbol.to_string())
15    }
16
17    /// Create a letter from multiple symbols
18    pub fn from_symbols(symbols: Vec<String>) -> Self {
19        if symbols.len() == 1 {
20            Letter::Single(symbols.into_iter().next().unwrap())
21        } else {
22            let mut sorted = symbols;
23            sorted.sort();
24            sorted.dedup();
25            Letter::Multiple(sorted)
26        }
27    }
28
29    /// Get the symbols represented by this letter
30    pub fn symbols(&self) -> String {
31        match self {
32            Letter::Single(s) => s.clone(),
33            Letter::Multiple(v) => v.join(","),
34        }
35    }
36
37    /// Get a string representation of this letter
38    pub fn name(&self) -> String {
39        match self {
40            Letter::Single(s) => format!("Letter('{}')", s),
41            Letter::Multiple(v) => {
42                format!(
43                    "Letter({})",
44                    v.iter()
45                        .map(|s| format!("'{}'", s))
46                        .collect::<Vec<_>>()
47                        .join(",")
48                )
49            }
50        }
51    }
52
53    /// Check if this is an empty letter
54    pub fn is_empty(&self) -> bool {
55        false
56    }
57
58    /// Deserialize a comma-separated string of letter names into a Letter
59    pub fn deserialize(str_letters: &str, possible_letters: &[Letter]) -> Result<Letter, String> {
60        let mut letters = Vec::new();
61        for str_letter in str_letters.split(',') {
62            let found = possible_letters
63                .iter()
64                .find(|l| l.name() == str_letter)
65                .ok_or_else(|| format!("Cannot find any letter that fit with '{}'", str_letter))?;
66            letters.push(found.clone());
67        }
68
69        if letters.len() == 1 {
70            Ok(letters[0].clone())
71        } else {
72            let symbols: Vec<String> = letters
73                .iter()
74                .flat_map(|l| match l {
75                    Letter::Single(s) => vec![s.clone()],
76                    Letter::Multiple(v) => v.clone(),
77                })
78                .collect();
79            Ok(Letter::from_symbols(symbols))
80        }
81    }
82}
83
84impl PartialEq for Letter {
85    fn eq(&self, other: &Self) -> bool {
86        match (self, other) {
87            (Letter::Single(a), Letter::Single(b)) => a == b,
88            (Letter::Multiple(a), Letter::Multiple(b)) => a == b,
89            _ => false,
90        }
91    }
92}
93
94impl Eq for Letter {}
95
96impl Hash for Letter {
97    fn hash<H: Hasher>(&self, state: &mut H) {
98        match self {
99            Letter::Single(s) => {
100                0u8.hash(state);
101                s.hash(state);
102            }
103            Letter::Multiple(v) => {
104                1u8.hash(state);
105                v.hash(state);
106            }
107        }
108    }
109}
110
111impl fmt::Display for Letter {
112    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113        write!(f, "{}", self.name())
114    }
115}
116
117impl fmt::Debug for Letter {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        write!(f, "{}", self.name())
120    }
121}
122
123/// Represents an empty letter, used as the identity element for words
124#[derive(Clone, PartialEq, Eq)]
125pub struct EmptyLetter;
126
127impl EmptyLetter {
128    pub fn new() -> Self {
129        EmptyLetter
130    }
131}
132
133impl Default for EmptyLetter {
134    fn default() -> Self {
135        EmptyLetter::new()
136    }
137}
138
139impl Hash for EmptyLetter {
140    fn hash<H: Hasher>(&self, state: &mut H) {
141        "EmptyLetter".hash(state);
142    }
143}
144
145impl fmt::Display for EmptyLetter {
146    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147        write!(f, "EmptyLetter")
148    }
149}
150
151impl fmt::Debug for EmptyLetter {
152    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153        write!(f, "EmptyLetter")
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_letter_creation() {
163        let letter = Letter::new("a");
164        assert_eq!(letter.name(), "Letter('a')");
165    }
166
167    #[test]
168    fn test_letter_equality() {
169        let l1 = Letter::new("a");
170        let l2 = Letter::new("a");
171        let l3 = Letter::new("b");
172
173        assert_eq!(l1, l2);
174        assert_ne!(l1, l3);
175    }
176
177    #[test]
178    fn test_empty_letter() {
179        let empty = EmptyLetter::new();
180        assert_eq!(empty.to_string(), "EmptyLetter");
181    }
182}