icu_plurals/provider/rules/runtime/
resolver.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::operands::PluralOperands;
6use crate::provider::rules::runtime::ast;
7
8/// <div class="stab unstable">
9/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
10/// including in SemVer minor releases. In particular, the `DataProvider` implementations are only
11/// guaranteed to match with this version's `*_unstable` providers. Use with caution.
12/// </div>
13#[inline]
14pub fn test_rule(rule: &ast::Rule, operands: &PluralOperands) -> bool {
15    // This algorithm is a simple non-recursive interpreter of the
16    // [UTS #35: Language Plural Rules].
17    //
18    // The algorithm exploits the fact that plural rules syntax is a simple
19    // logical operator expression composition with maximum depth of one
20    // level of `OR` expression.
21    //
22    // That means that any `AND` expression accumulates to a single boolean
23    // result which either results in a test passing, or the next set
24    // of `AND` relations is evaluated after the `OR`.
25    //
26    // To achieve that, the algorithm traverses the relations from left to right
27    // collecting all matching relations into a temporary `left` variable for
28    // as long as they are followed by the `AND` operator.
29    //
30    // If any relation fails to match, the `left` variable becomes `false` and the
31    // interpreter skips to the first `OR` operator, rejects the left side, and
32    // evaluates the right as a candidate and so on.
33    //
34    // [UTS #35: Language Plural Rules]: https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules
35
36    let mut left = true;
37
38    for relation in rule.0.iter() {
39        let relation = relation.as_relation();
40        if left && relation.aopo.and_or == ast::AndOr::Or {
41            return true;
42        }
43        if left || relation.aopo.and_or == ast::AndOr::Or {
44            left = test_relation(&relation, operands);
45        }
46    }
47    left
48}
49
50#[inline]
51fn test_relation(relation: &ast::Relation, operands: &PluralOperands) -> bool {
52    let result = if let Some(value) = get_value(relation, operands) {
53        relation.range_list.iter().any(|range| match range {
54            ast::RangeOrValue::Value(v) => u64::from(v) == value,
55            ast::RangeOrValue::Range(min, max) => {
56                value >= u64::from(min) && value <= u64::from(max)
57            }
58        })
59    } else {
60        false
61    };
62    match relation.aopo.polarity {
63        ast::Polarity::Negative => !result,
64        ast::Polarity::Positive => result,
65    }
66}
67
68#[inline]
69fn get_value(relation: &ast::Relation, operands: &PluralOperands) -> Option<u64> {
70    let value = match relation.aopo.operand {
71        ast::Operand::N if operands.w == 0 => operands.i,
72        ast::Operand::N => return None,
73        ast::Operand::I => operands.i,
74        ast::Operand::F => operands.f,
75        ast::Operand::V => operands.v as u64,
76        ast::Operand::W => operands.w as u64,
77        ast::Operand::T => operands.t,
78        ast::Operand::C | ast::Operand::E => operands.c as u64,
79    };
80    if relation.modulo > 0 {
81        value.checked_rem_euclid(relation.modulo.into())
82    } else {
83        Some(value)
84    }
85}