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}