intl_pluralrules/
operands.rs

1//! Plural operands in compliance with [CLDR Plural Rules](https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules).
2//!
3//! See [full operands description](https://unicode.org/reports/tr35/tr35-numbers.html#Operands).
4//!
5//! # Examples
6//!
7//! From int
8//!
9//! ```
10//! use std::convert::TryFrom;
11//! use intl_pluralrules::operands::*;
12//! assert_eq!(Ok(PluralOperands {
13//!    n: 2_f64,
14//!    i: 2,
15//!    v: 0,
16//!    w: 0,
17//!    f: 0,
18//!    t: 0,
19//! }), PluralOperands::try_from(2))
20//! ```
21//!
22//! From float
23//!
24//! ```
25//! use std::convert::TryFrom;
26//! use intl_pluralrules::operands::*;
27//! assert_eq!(Ok(PluralOperands {
28//!    n: 1234.567_f64,
29//!    i: 1234,
30//!    v: 3,
31//!    w: 3,
32//!    f: 567,
33//!    t: 567,
34//! }), PluralOperands::try_from("-1234.567"))
35//! ```
36//!
37//! From &str
38//!
39//! ```
40//! use std::convert::TryFrom;
41//! use intl_pluralrules::operands::*;
42//! assert_eq!(Ok(PluralOperands {
43//!    n: 123.45_f64,
44//!    i: 123,
45//!    v: 2,
46//!    w: 2,
47//!    f: 45,
48//!    t: 45,
49//! }), PluralOperands::try_from(123.45))
50//! ```
51#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
52use std::convert::TryFrom;
53use std::isize;
54use std::str::FromStr;
55
56/// A full plural operands representation of a number. See [CLDR Plural Rules](https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules) for complete operands description.
57#[derive(Debug, PartialEq)]
58pub struct PluralOperands {
59    /// Absolute value of input
60    pub n: f64,
61    /// Integer value of input
62    pub i: u64,
63    /// Number of visible fraction digits with trailing zeros
64    pub v: usize,
65    /// Number of visible fraction digits without trailing zeros
66    pub w: usize,
67    /// Visible fraction digits with trailing zeros
68    pub f: u64,
69    /// Visible fraction digits without trailing zeros
70    pub t: u64,
71}
72
73impl<'a> TryFrom<&'a str> for PluralOperands {
74    type Error = &'static str;
75
76    fn try_from(input: &'a str) -> Result<Self, Self::Error> {
77        let abs_str = if input.starts_with('-') {
78            &input[1..]
79        } else {
80            &input
81        };
82
83        let absolute_value = f64::from_str(&abs_str).map_err(|_| "Incorrect number passed!")?;
84
85        let integer_digits;
86        let num_fraction_digits0;
87        let num_fraction_digits;
88        let fraction_digits0;
89        let fraction_digits;
90
91        if let Some(dec_pos) = abs_str.find('.') {
92            let int_str = &abs_str[..dec_pos];
93            let dec_str = &abs_str[(dec_pos + 1)..];
94
95            integer_digits =
96                u64::from_str(&int_str).map_err(|_| "Could not convert string to integer!")?;
97
98            let backtrace = dec_str.trim_end_matches('0');
99
100            num_fraction_digits0 = dec_str.len() as usize;
101            num_fraction_digits = backtrace.len() as usize;
102            fraction_digits0 =
103                u64::from_str(dec_str).map_err(|_| "Could not convert string to integer!")?;
104            fraction_digits = u64::from_str(backtrace).unwrap_or(0);
105        } else {
106            integer_digits = absolute_value as u64;
107            num_fraction_digits0 = 0;
108            num_fraction_digits = 0;
109            fraction_digits0 = 0;
110            fraction_digits = 0;
111        }
112
113        Ok(PluralOperands {
114            n: absolute_value,
115            i: integer_digits,
116            v: num_fraction_digits0,
117            w: num_fraction_digits,
118            f: fraction_digits0,
119            t: fraction_digits,
120        })
121    }
122}
123
124macro_rules! impl_integer_type {
125    ($ty:ident) => {
126        impl From<$ty> for PluralOperands {
127            fn from(input: $ty) -> Self {
128                // XXXManishearth converting from u32 or u64 to isize may wrap
129                PluralOperands {
130                    n: input as f64,
131                    i: input as u64,
132                    v: 0,
133                    w: 0,
134                    f: 0,
135                    t: 0,
136                }
137            }
138        }
139    };
140    ($($ty:ident)+) => {
141        $(impl_integer_type!($ty);)+
142    };
143}
144
145macro_rules! impl_signed_integer_type {
146    ($ty:ident) => {
147        impl TryFrom<$ty> for PluralOperands {
148            type Error = &'static str;
149            fn try_from(input: $ty) -> Result<Self, Self::Error> {
150                // XXXManishearth converting from i64 to isize may wrap
151                let x = (input as isize).checked_abs().ok_or("Number too big")?;
152                Ok(PluralOperands {
153                    n: x as f64,
154                    i: x as u64,
155                    v: 0,
156                    w: 0,
157                    f: 0,
158                    t: 0,
159                })
160            }
161        }
162    };
163    ($($ty:ident)+) => {
164        $(impl_signed_integer_type!($ty);)+
165    };
166}
167
168macro_rules! impl_convert_type {
169    ($ty:ident) => {
170        impl TryFrom<$ty> for PluralOperands {
171            type Error = &'static str;
172            fn try_from(input: $ty) -> Result<Self, Self::Error> {
173                let as_str: &str = &input.to_string();
174                PluralOperands::try_from(as_str)
175            }
176        }
177    };
178    ($($ty:ident)+) => {
179        $(impl_convert_type!($ty);)+
180    };
181}
182
183impl_integer_type!(u8 u16 u32 u64 usize);
184impl_signed_integer_type!(i8 i16 i32 i64 isize);
185// XXXManishearth we can likely have dedicated float impls here
186impl_convert_type!(f32 f64 String);