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))] |
52 | use std::convert::TryFrom; |
53 | use std::isize; |
54 | use 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)] |
58 | pub 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 | |
73 | impl<'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 | |
124 | macro_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 | |
145 | macro_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 | |
168 | macro_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 | |
183 | impl_integer_type!(u8 u16 u32 u64 usize); |
184 | impl_signed_integer_type!(i8 i16 i32 i64 isize); |
185 | // XXXManishearth we can likely have dedicated float impls here |
186 | impl_convert_type!(f32 f64 String); |
187 | |