gettext_ng/
plurals.rs

1use crate::Error;
2
3use self::Resolver::*;
4
5#[derive(Clone, Debug)]
6pub enum Resolver {
7    /// A boolean expression
8    /// Use Ast::parse to get an Ast
9    Expr(Ast),
10    /// A function
11    Function(fn(u64) -> usize),
12}
13
14/// Finds the index of a pattern, outside of parenthesis
15fn index_of(src: &str, pat: &str) -> Option<usize> {
16    src.chars()
17        .fold(
18            (None, 0, 0, 0),
19            |(match_index, i, n_matches, paren_level), ch| {
20                if let Some(x) = match_index {
21                    (Some(x), i, n_matches, paren_level)
22                } else {
23                    let new_par_lvl = match ch {
24                        '(' => paren_level + 1,
25                        ')' => paren_level - 1,
26                        _ => paren_level,
27                    };
28
29                    if Some(ch) == pat.chars().nth(n_matches) {
30                        let length = n_matches + 1;
31                        if length == pat.len() && new_par_lvl == 0 {
32                            (Some(i - n_matches), i + 1, length, new_par_lvl)
33                        } else {
34                            (match_index, i + 1, length, new_par_lvl)
35                        }
36                    } else {
37                        (match_index, i + 1, 0, new_par_lvl)
38                    }
39                }
40            },
41        )
42        .0
43}
44
45use self::Ast::*;
46#[derive(Clone, Debug, PartialEq)]
47pub enum Ast {
48    /// A ternary expression
49    /// x ? a : b
50    ///
51    /// the three Ast<'a> are respectively x, a and b.
52    Ternary(Box<Ast>, Box<Ast>, Box<Ast>),
53    /// The n variable.
54    N,
55    /// Integer literals.
56    Integer(u64),
57    /// Binary operators.
58    Op(Operator, Box<Ast>, Box<Ast>),
59    /// ! operator.
60    Not(Box<Ast>),
61}
62
63#[derive(Clone, Debug, PartialEq)]
64pub enum Operator {
65    Equal,
66    NotEqual,
67    GreaterOrEqual,
68    SmallerOrEqual,
69    Greater,
70    Smaller,
71    And,
72    Or,
73    Modulo,
74}
75
76impl Ast {
77    fn resolve(&self, n: u64) -> usize {
78        match *self {
79            Ternary(ref cond, ref ok, ref nok) => {
80                if cond.resolve(n) == 0 {
81                    nok.resolve(n)
82                } else {
83                    ok.resolve(n)
84                }
85            }
86            N => n as usize,
87            Integer(x) => x as usize,
88            Op(ref op, ref lhs, ref rhs) => match *op {
89                Operator::Equal => (lhs.resolve(n) == rhs.resolve(n)) as usize,
90                Operator::NotEqual => (lhs.resolve(n) != rhs.resolve(n)) as usize,
91                Operator::GreaterOrEqual => (lhs.resolve(n) >= rhs.resolve(n)) as usize,
92                Operator::SmallerOrEqual => (lhs.resolve(n) <= rhs.resolve(n)) as usize,
93                Operator::Greater => (lhs.resolve(n) > rhs.resolve(n)) as usize,
94                Operator::Smaller => (lhs.resolve(n) < rhs.resolve(n)) as usize,
95                Operator::And => (lhs.resolve(n) != 0 && rhs.resolve(n) != 0) as usize,
96                Operator::Or => (lhs.resolve(n) != 0 || rhs.resolve(n) != 0) as usize,
97                Operator::Modulo => lhs.resolve(n) % rhs.resolve(n),
98            },
99            Not(ref val) => match val.resolve(n) {
100                0 => 1,
101                _ => 0,
102            },
103        }
104    }
105
106    pub fn parse(src: &str) -> Result<Ast, Error> {
107        Self::parse_parens(src.trim())
108    }
109
110    fn parse_parens(src: &str) -> Result<Ast, Error> {
111        if src.starts_with('(') {
112            let end = src[1..src.len() - 1]
113                .chars()
114                .fold((1, 2), |(level, index), ch| match (level, ch) {
115                    (0, '(') => (level + 1, index + 1),
116                    (0, _) => (level, index),
117                    (_, '(') => (level + 1, index + 1),
118                    (_, ')') => (level - 1, index + 1),
119                    (_, _) => (level, index + 1),
120                })
121                .1;
122            if end == src.len() {
123                Ast::parse(src[1..src.len() - 1].trim())
124            } else {
125                Ast::parse_and(src.trim())
126            }
127        } else {
128            Ast::parse_and(src.trim())
129        }
130    }
131
132    fn parse_and(src: &str) -> Result<Ast, Error> {
133        if let Some(i) = index_of(src, "&&") {
134            Ok(Ast::Op(
135                Operator::And,
136                Box::new(Ast::parse(&src[0..i])?),
137                Box::new(Ast::parse(&src[i + 2..])?),
138            ))
139        } else {
140            Self::parse_or(src)
141        }
142    }
143
144    fn parse_or(src: &str) -> Result<Ast, Error> {
145        if let Some(i) = index_of(src, "||") {
146            Ok(Ast::Op(
147                Operator::Or,
148                Box::new(Ast::parse(&src[0..i])?),
149                Box::new(Ast::parse(&src[i + 2..])?),
150            ))
151        } else {
152            Self::parse_ternary(src)
153        }
154    }
155
156    fn parse_ternary(src: &str) -> Result<Ast, Error> {
157        if let Some(i) = index_of(src, "?") {
158            if let Some(l) = index_of(src, ":") {
159                Ok(Ast::Ternary(
160                    Box::new(Ast::parse(&src[0..i])?),
161                    Box::new(Ast::parse(&src[i + 1..l])?),
162                    Box::new(Ast::parse(&src[l + 1..])?),
163                ))
164            } else {
165                Err(Error::PluralParsing)
166            }
167        } else {
168            Self::parse_ge(src)
169        }
170    }
171
172    fn parse_ge(src: &str) -> Result<Ast, Error> {
173        if let Some(i) = index_of(src, ">=") {
174            Ok(Ast::Op(
175                Operator::GreaterOrEqual,
176                Box::new(Ast::parse(&src[0..i])?),
177                Box::new(Ast::parse(&src[i + 2..])?),
178            ))
179        } else {
180            Self::parse_gt(src)
181        }
182    }
183
184    fn parse_gt(src: &str) -> Result<Ast, Error> {
185        if let Some(i) = index_of(src, ">") {
186            Ok(Ast::Op(
187                Operator::Greater,
188                Box::new(Ast::parse(&src[0..i])?),
189                Box::new(Ast::parse(&src[i + 1..])?),
190            ))
191        } else {
192            Self::parse_le(src)
193        }
194    }
195
196    fn parse_le(src: &str) -> Result<Ast, Error> {
197        if let Some(i) = index_of(src, "<=") {
198            Ok(Ast::Op(
199                Operator::SmallerOrEqual,
200                Box::new(Ast::parse(&src[0..i])?),
201                Box::new(Ast::parse(&src[i + 2..])?),
202            ))
203        } else {
204            Self::parse_lt(src)
205        }
206    }
207
208    fn parse_lt(src: &str) -> Result<Ast, Error> {
209        if let Some(i) = index_of(src, "<") {
210            Ok(Ast::Op(
211                Operator::Smaller,
212                Box::new(Ast::parse(&src[0..i])?),
213                Box::new(Ast::parse(&src[i + 1..])?),
214            ))
215        } else {
216            Self::parse_eq(src)
217        }
218    }
219
220    fn parse_eq(src: &str) -> Result<Ast, Error> {
221        if let Some(i) = index_of(src, "==") {
222            Ok(Ast::Op(
223                Operator::Equal,
224                Box::new(Ast::parse(&src[0..i])?),
225                Box::new(Ast::parse(&src[i + 2..])?),
226            ))
227        } else {
228            Self::parse_neq(src)
229        }
230    }
231
232    fn parse_neq(src: &str) -> Result<Ast, Error> {
233        if let Some(i) = index_of(src, "!=") {
234            Ok(Ast::Op(
235                Operator::NotEqual,
236                Box::new(Ast::parse(&src[0..i])?),
237                Box::new(Ast::parse(&src[i + 2..])?),
238            ))
239        } else {
240            Self::parse_mod(src)
241        }
242    }
243    fn parse_mod(src: &str) -> Result<Ast, Error> {
244        if let Some(i) = index_of(src, "%") {
245            Ok(Ast::Op(
246                Operator::Modulo,
247                Box::new(Ast::parse(&src[0..i])?),
248                Box::new(Ast::parse(&src[i + 1..])?),
249            ))
250        } else {
251            Self::parse_not(src.trim())
252        }
253    }
254
255    fn parse_not(src: &str) -> Result<Ast, Error> {
256        if index_of(src, "!") == Some(0) {
257            Ok(Ast::Not(Box::new(Ast::parse(&src[1..])?)))
258        } else {
259            Self::parse_int(src.trim())
260        }
261    }
262
263    fn parse_int(src: &str) -> Result<Ast, Error> {
264        if let Ok(x) = u64::from_str_radix(src, 10) {
265            Ok(Ast::Integer(x))
266        } else {
267            Self::parse_n(src.trim())
268        }
269    }
270
271    fn parse_n(src: &str) -> Result<Ast, Error> {
272        if src == "n" {
273            Ok(Ast::N)
274        } else {
275            Err(Error::PluralParsing)
276        }
277    }
278}
279
280impl Resolver {
281    /// Returns the number of the correct plural form
282    /// for `n` objects, as defined by the rule contained in this resolver.
283    pub fn resolve(&self, n: u64) -> usize {
284        match *self {
285            Expr(ref ast) => ast.resolve(n),
286            Function(ref f) => f(n),
287        }
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_expr_resolver() {
297        assert_eq!(Expr(N).resolve(42), 42);
298    }
299
300    #[test]
301    fn test_parser() {
302        assert_eq!(
303            Ast::parse("n == 42 ? n : 6 && n < 7").expect("Invalid plural"),
304            Ast::Op(
305                Operator::And,
306                Box::new(Ast::Ternary(
307                    Box::new(Ast::Op(
308                        Operator::Equal,
309                        Box::new(Ast::N),
310                        Box::new(Ast::Integer(42))
311                    )),
312                    Box::new(Ast::N),
313                    Box::new(Ast::Integer(6))
314                )),
315                Box::new(Ast::Op(
316                    Operator::Smaller,
317                    Box::new(Ast::N),
318                    Box::new(Ast::Integer(7))
319                ))
320            )
321        );
322
323        assert_eq!(Ast::parse("(n)").expect("Invalid plural"), Ast::N);
324
325        assert_eq!(
326            Ast::parse("(n == 1 || n == 2) ? 0 : 1").expect("Invalid plural"),
327            Ast::Ternary(
328                Box::new(Ast::Op(
329                    Operator::Or,
330                    Box::new(Ast::Op(
331                        Operator::Equal,
332                        Box::new(Ast::N),
333                        Box::new(Ast::Integer(1))
334                    )),
335                    Box::new(Ast::Op(
336                        Operator::Equal,
337                        Box::new(Ast::N),
338                        Box::new(Ast::Integer(2))
339                    ))
340                )),
341                Box::new(Ast::Integer(0)),
342                Box::new(Ast::Integer(1))
343            )
344        );
345
346        let ru_plural = "((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3))";
347        assert!(Ast::parse(ru_plural).is_ok());
348    }
349}