| 1 | use std::borrow::Cow; | 
| 2 | use std::convert::TryInto; | 
|---|
| 3 | use std::default::Default; | 
|---|
| 4 | use std::str::FromStr; | 
|---|
| 5 |  | 
|---|
| 6 | use intl_pluralrules::operands::PluralOperands; | 
|---|
| 7 |  | 
|---|
| 8 | use crate::args::FluentArgs; | 
|---|
| 9 | use crate::types::FluentValue; | 
|---|
| 10 |  | 
|---|
| 11 | #[ derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] | 
|---|
| 12 | pub enum FluentNumberStyle { | 
|---|
| 13 | Decimal, | 
|---|
| 14 | Currency, | 
|---|
| 15 | Percent, | 
|---|
| 16 | } | 
|---|
| 17 |  | 
|---|
| 18 | impl std::default::Default for FluentNumberStyle { | 
|---|
| 19 | fn default() -> Self { | 
|---|
| 20 | Self::Decimal | 
|---|
| 21 | } | 
|---|
| 22 | } | 
|---|
| 23 |  | 
|---|
| 24 | impl From<&str> for FluentNumberStyle { | 
|---|
| 25 | fn from(input: &str) -> Self { | 
|---|
| 26 | match input { | 
|---|
| 27 | "decimal"=> Self::Decimal, | 
|---|
| 28 | "currency"=> Self::Currency, | 
|---|
| 29 | "percent"=> Self::Percent, | 
|---|
| 30 | _ => Self::default(), | 
|---|
| 31 | } | 
|---|
| 32 | } | 
|---|
| 33 | } | 
|---|
| 34 |  | 
|---|
| 35 | #[ derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] | 
|---|
| 36 | pub enum FluentNumberCurrencyDisplayStyle { | 
|---|
| 37 | Symbol, | 
|---|
| 38 | Code, | 
|---|
| 39 | Name, | 
|---|
| 40 | } | 
|---|
| 41 |  | 
|---|
| 42 | impl std::default::Default for FluentNumberCurrencyDisplayStyle { | 
|---|
| 43 | fn default() -> Self { | 
|---|
| 44 | Self::Symbol | 
|---|
| 45 | } | 
|---|
| 46 | } | 
|---|
| 47 |  | 
|---|
| 48 | impl From<&str> for FluentNumberCurrencyDisplayStyle { | 
|---|
| 49 | fn from(input: &str) -> Self { | 
|---|
| 50 | match input { | 
|---|
| 51 | "symbol"=> Self::Symbol, | 
|---|
| 52 | "code"=> Self::Code, | 
|---|
| 53 | "name"=> Self::Name, | 
|---|
| 54 | _ => Self::default(), | 
|---|
| 55 | } | 
|---|
| 56 | } | 
|---|
| 57 | } | 
|---|
| 58 |  | 
|---|
| 59 | #[ derive(Debug, Clone, Hash, PartialEq, Eq)] | 
|---|
| 60 | pub struct FluentNumberOptions { | 
|---|
| 61 | pub style: FluentNumberStyle, | 
|---|
| 62 | pub currency: Option<String>, | 
|---|
| 63 | pub currency_display: FluentNumberCurrencyDisplayStyle, | 
|---|
| 64 | pub use_grouping: bool, | 
|---|
| 65 | pub minimum_integer_digits: Option<usize>, | 
|---|
| 66 | pub minimum_fraction_digits: Option<usize>, | 
|---|
| 67 | pub maximum_fraction_digits: Option<usize>, | 
|---|
| 68 | pub minimum_significant_digits: Option<usize>, | 
|---|
| 69 | pub maximum_significant_digits: Option<usize>, | 
|---|
| 70 | } | 
|---|
| 71 |  | 
|---|
| 72 | impl Default for FluentNumberOptions { | 
|---|
| 73 | fn default() -> Self { | 
|---|
| 74 | Self { | 
|---|
| 75 | style: Default::default(), | 
|---|
| 76 | currency: None, | 
|---|
| 77 | currency_display: Default::default(), | 
|---|
| 78 | use_grouping: true, | 
|---|
| 79 | minimum_integer_digits: None, | 
|---|
| 80 | minimum_fraction_digits: None, | 
|---|
| 81 | maximum_fraction_digits: None, | 
|---|
| 82 | minimum_significant_digits: None, | 
|---|
| 83 | maximum_significant_digits: None, | 
|---|
| 84 | } | 
|---|
| 85 | } | 
|---|
| 86 | } | 
|---|
| 87 |  | 
|---|
| 88 | impl FluentNumberOptions { | 
|---|
| 89 | pub fn merge(&mut self, opts: &FluentArgs) { | 
|---|
| 90 | for (key, value) in opts.iter() { | 
|---|
| 91 | match (key, value) { | 
|---|
| 92 | ( "style", FluentValue::String(n)) => { | 
|---|
| 93 | self.style = n.as_ref().into(); | 
|---|
| 94 | } | 
|---|
| 95 | ( "currency", FluentValue::String(n)) => { | 
|---|
| 96 | self.currency = Some(n.to_string()); | 
|---|
| 97 | } | 
|---|
| 98 | ( "currencyDisplay", FluentValue::String(n)) => { | 
|---|
| 99 | self.currency_display = n.as_ref().into(); | 
|---|
| 100 | } | 
|---|
| 101 | ( "useGrouping", FluentValue::String(n)) => { | 
|---|
| 102 | self.use_grouping = n != "false"; | 
|---|
| 103 | } | 
|---|
| 104 | ( "minimumIntegerDigits", FluentValue::Number(n)) => { | 
|---|
| 105 | self.minimum_integer_digits = Some(n.into()); | 
|---|
| 106 | } | 
|---|
| 107 | ( "minimumFractionDigits", FluentValue::Number(n)) => { | 
|---|
| 108 | self.minimum_fraction_digits = Some(n.into()); | 
|---|
| 109 | } | 
|---|
| 110 | ( "maximumFractionDigits", FluentValue::Number(n)) => { | 
|---|
| 111 | self.maximum_fraction_digits = Some(n.into()); | 
|---|
| 112 | } | 
|---|
| 113 | ( "minimumSignificantDigits", FluentValue::Number(n)) => { | 
|---|
| 114 | self.minimum_significant_digits = Some(n.into()); | 
|---|
| 115 | } | 
|---|
| 116 | ( "maximumSignificantDigits", FluentValue::Number(n)) => { | 
|---|
| 117 | self.maximum_significant_digits = Some(n.into()); | 
|---|
| 118 | } | 
|---|
| 119 | _ => {} | 
|---|
| 120 | } | 
|---|
| 121 | } | 
|---|
| 122 | } | 
|---|
| 123 | } | 
|---|
| 124 |  | 
|---|
| 125 | #[ derive(Debug, PartialEq, Clone)] | 
|---|
| 126 | pub struct FluentNumber { | 
|---|
| 127 | pub value: f64, | 
|---|
| 128 | pub options: FluentNumberOptions, | 
|---|
| 129 | } | 
|---|
| 130 |  | 
|---|
| 131 | impl FluentNumber { | 
|---|
| 132 | pub const fn new(value: f64, options: FluentNumberOptions) -> Self { | 
|---|
| 133 | Self { value, options } | 
|---|
| 134 | } | 
|---|
| 135 |  | 
|---|
| 136 | pub fn as_string(&self) -> Cow<'static, str> { | 
|---|
| 137 | let mut val: String = self.value.to_string(); | 
|---|
| 138 | if let Some(minfd: usize) = self.options.minimum_fraction_digits { | 
|---|
| 139 | if let Some(pos: usize) = val.find( '.') { | 
|---|
| 140 | let frac_num: usize = val.len() - pos - 1; | 
|---|
| 141 | let missing: usize = if frac_num > minfd { | 
|---|
| 142 | 0 | 
|---|
| 143 | } else { | 
|---|
| 144 | minfd - frac_num | 
|---|
| 145 | }; | 
|---|
| 146 | val = format!( "{}{} ", val, "0".repeat(missing)); | 
|---|
| 147 | } else { | 
|---|
| 148 | val = format!( "{} .{} ", val, "0".repeat(minfd)); | 
|---|
| 149 | } | 
|---|
| 150 | } | 
|---|
| 151 | val.into() | 
|---|
| 152 | } | 
|---|
| 153 | } | 
|---|
| 154 |  | 
|---|
| 155 | impl FromStr for FluentNumber { | 
|---|
| 156 | type Err = std::num::ParseFloatError; | 
|---|
| 157 |  | 
|---|
| 158 | fn from_str(input: &str) -> Result<Self, Self::Err> { | 
|---|
| 159 | f64::from_str(input).map(|n: f64| { | 
|---|
| 160 | let mfd: Option = input.find( '.').map(|pos: usize| input.len() - pos - 1); | 
|---|
| 161 | let opts: FluentNumberOptions = FluentNumberOptions { | 
|---|
| 162 | minimum_fraction_digits: mfd, | 
|---|
| 163 | ..Default::default() | 
|---|
| 164 | }; | 
|---|
| 165 | Self::new(value:n, options:opts) | 
|---|
| 166 | }) | 
|---|
| 167 | } | 
|---|
| 168 | } | 
|---|
| 169 |  | 
|---|
| 170 | impl<'l> From<FluentNumber> for FluentValue<'l> { | 
|---|
| 171 | fn from(input: FluentNumber) -> Self { | 
|---|
| 172 | FluentValue::Number(input) | 
|---|
| 173 | } | 
|---|
| 174 | } | 
|---|
| 175 |  | 
|---|
| 176 | macro_rules! from_num { | 
|---|
| 177 | ($num:ty) => { | 
|---|
| 178 | impl From<$num> for FluentNumber { | 
|---|
| 179 | fn from(n: $num) -> Self { | 
|---|
| 180 | Self { | 
|---|
| 181 | value: n as f64, | 
|---|
| 182 | options: FluentNumberOptions::default(), | 
|---|
| 183 | } | 
|---|
| 184 | } | 
|---|
| 185 | } | 
|---|
| 186 | impl From<&$num> for FluentNumber { | 
|---|
| 187 | fn from(n: &$num) -> Self { | 
|---|
| 188 | Self { | 
|---|
| 189 | value: *n as f64, | 
|---|
| 190 | options: FluentNumberOptions::default(), | 
|---|
| 191 | } | 
|---|
| 192 | } | 
|---|
| 193 | } | 
|---|
| 194 | impl From<FluentNumber> for $num { | 
|---|
| 195 | fn from(input: FluentNumber) -> Self { | 
|---|
| 196 | input.value as $num | 
|---|
| 197 | } | 
|---|
| 198 | } | 
|---|
| 199 | impl From<&FluentNumber> for $num { | 
|---|
| 200 | fn from(input: &FluentNumber) -> Self { | 
|---|
| 201 | input.value as $num | 
|---|
| 202 | } | 
|---|
| 203 | } | 
|---|
| 204 | impl From<$num> for FluentValue<'_> { | 
|---|
| 205 | fn from(n: $num) -> Self { | 
|---|
| 206 | FluentValue::Number(n.into()) | 
|---|
| 207 | } | 
|---|
| 208 | } | 
|---|
| 209 | impl From<&$num> for FluentValue<'_> { | 
|---|
| 210 | fn from(n: &$num) -> Self { | 
|---|
| 211 | FluentValue::Number(n.into()) | 
|---|
| 212 | } | 
|---|
| 213 | } | 
|---|
| 214 | }; | 
|---|
| 215 | ($($num:ty)+) => { | 
|---|
| 216 | $(from_num!($num);)+ | 
|---|
| 217 | }; | 
|---|
| 218 | } | 
|---|
| 219 |  | 
|---|
| 220 | impl From<&FluentNumber> for PluralOperands { | 
|---|
| 221 | fn from(input: &FluentNumber) -> Self { | 
|---|
| 222 | let mut operands: Self = input | 
|---|
| 223 | .value | 
|---|
| 224 | .try_into() | 
|---|
| 225 | .expect(msg: "Failed to generate operands out of FluentNumber"); | 
|---|
| 226 | if let Some(mfd: usize) = input.options.minimum_fraction_digits { | 
|---|
| 227 | if mfd > operands.v { | 
|---|
| 228 | operands.f *= 10_u64.pow(exp:mfd as u32 - operands.v as u32); | 
|---|
| 229 | operands.v = mfd; | 
|---|
| 230 | } | 
|---|
| 231 | } | 
|---|
| 232 | // XXX: Add support for other options. | 
|---|
| 233 | operands | 
|---|
| 234 | } | 
|---|
| 235 | } | 
|---|
| 236 |  | 
|---|
| 237 | from_num!(i8 i16 i32 i64 i128 isize); | 
|---|
| 238 | from_num!(u8 u16 u32 u64 u128 usize); | 
|---|
| 239 | from_num!(f32 f64); | 
|---|
| 240 |  | 
|---|
| 241 | #[ cfg(test)] | 
|---|
| 242 | mod tests { | 
|---|
| 243 | use crate::types::FluentValue; | 
|---|
| 244 |  | 
|---|
| 245 | #[ test] | 
|---|
| 246 | fn value_from_copy_ref() { | 
|---|
| 247 | let x = 1i16; | 
|---|
| 248 | let y = &x; | 
|---|
| 249 | let z: FluentValue = y.into(); | 
|---|
| 250 | assert_eq!(z, FluentValue::try_number( "1")); | 
|---|
| 251 | } | 
|---|
| 252 | } | 
|---|
| 253 |  | 
|---|