1use crate::compiler::prelude::*;
2use rust_decimal::{Decimal, prelude::FromPrimitive};
3use std::sync::LazyLock;
4
5static DEFAULT_DECIMAL_SEPARATOR: LazyLock<Value> =
6 LazyLock::new(|| Value::Bytes(Bytes::from(".")));
7
8static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
9 vec![
10 Parameter::required(
11 "value",
12 kind::INTEGER | kind::FLOAT,
13 "The number to format as a string.",
14 ),
15 Parameter::optional(
16 "scale",
17 kind::INTEGER,
18 "The number of decimal places to display.",
19 ),
20 Parameter::optional(
21 "decimal_separator",
22 kind::BYTES,
23 "The character to use between the whole and decimal parts of the number.",
24 )
25 .default(&DEFAULT_DECIMAL_SEPARATOR),
26 Parameter::optional(
27 "grouping_separator",
28 kind::BYTES,
29 "The character to use between each thousands part of the number.",
30 ),
31 ]
32});
33
34fn format_number(
35 value: Value,
36 scale: Option<Value>,
37 grouping_separator: Option<Value>,
38 decimal_separator: Value,
39) -> Resolved {
40 let value: Decimal = match value {
41 Value::Integer(v) => v.into(),
42 Value::Float(v) => Decimal::from_f64(*v).expect("not NaN"),
43 value => {
44 return Err(ValueError::Expected {
45 got: value.kind(),
46 expected: Kind::integer() | Kind::float(),
47 }
48 .into());
49 }
50 };
51 let scale = match scale {
52 Some(expr) => Some(expr.try_integer()?),
53 None => None,
54 };
55 let grouping_separator = match grouping_separator {
56 Some(expr) => Some(expr.try_bytes()?),
57 None => None,
58 };
59 let decimal_separator = decimal_separator.try_bytes()?;
60 let mut parts = value
62 .to_string()
63 .split('.')
64 .map(ToOwned::to_owned)
65 .collect::<Vec<String>>();
66 debug_assert!(parts.len() <= 2);
67 match scale {
69 Some(0) => parts.truncate(1),
70 Some(i) => {
71 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
73 let i = i as usize;
74
75 if parts.len() == 1 {
76 parts.push(String::new());
77 }
78
79 if i > parts[1].len() {
80 for _ in 0..i - parts[1].len() {
81 parts[1].push('0');
82 }
83 } else {
84 parts[1].truncate(i);
85 }
86 }
87 None => {}
88 }
89 if let Some(sep) = grouping_separator.as_deref() {
91 let sep = String::from_utf8_lossy(sep);
92 let start = parts[0].len() % 3;
93
94 let positions: Vec<usize> = parts[0]
95 .chars()
96 .skip(start)
97 .enumerate()
98 .map(|(i, _)| i)
99 .filter(|i| i % 3 == 0)
100 .collect();
101
102 for (i, pos) in positions.iter().enumerate() {
103 parts[0].insert_str(pos + (i * sep.len()) + start, &sep);
104 }
105 }
106 Ok(parts
108 .join(&String::from_utf8_lossy(&decimal_separator[..]))
109 .into())
110}
111
112#[derive(Clone, Copy, Debug)]
113pub struct FormatNumber;
114
115impl Function for FormatNumber {
116 fn identifier(&self) -> &'static str {
117 "format_number"
118 }
119
120 fn usage(&self) -> &'static str {
121 "Formats the `value` into a string representation of the number."
122 }
123
124 fn category(&self) -> &'static str {
125 Category::Number.as_ref()
126 }
127
128 fn return_kind(&self) -> u16 {
129 kind::BYTES
130 }
131
132 fn parameters(&self) -> &'static [Parameter] {
133 PARAMETERS.as_slice()
134 }
135
136 fn compile(
137 &self,
138 _state: &state::TypeState,
139 _ctx: &mut FunctionCompileContext,
140 arguments: ArgumentList,
141 ) -> Compiled {
142 let value = arguments.required("value");
143 let scale = arguments.optional("scale");
144 let decimal_separator = arguments.optional("decimal_separator");
145 let grouping_separator = arguments.optional("grouping_separator");
146
147 Ok(FormatNumberFn {
148 value,
149 scale,
150 decimal_separator,
151 grouping_separator,
152 }
153 .as_expr())
154 }
155
156 fn examples(&self) -> &'static [Example] {
157 &[
158 example! {
159 title: "Format a number (3 decimals)",
160 source: r#"format_number(1234567.89, 3, decimal_separator: ".", grouping_separator: ",")"#,
161 result: Ok("1,234,567.890"),
162 },
163 example! {
164 title: "Format a number with European-style separators",
165 source: r#"format_number(4672.4, decimal_separator: ",", grouping_separator: "_")"#,
166 result: Ok("4_672,4"),
167 },
168 example! {
169 title: "Format a number with a middle dot separator",
170 source: r#"format_number(4321.09, 3, decimal_separator: "·")"#,
171 result: Ok("4321·090"),
172 },
173 ]
174 }
175}
176
177#[derive(Clone, Debug)]
178struct FormatNumberFn {
179 value: Box<dyn Expression>,
180 scale: Option<Box<dyn Expression>>,
181 decimal_separator: Option<Box<dyn Expression>>,
182 grouping_separator: Option<Box<dyn Expression>>,
183}
184
185impl FunctionExpression for FormatNumberFn {
186 fn resolve(&self, ctx: &mut Context) -> Resolved {
187 let value = self.value.resolve(ctx)?;
188 let scale = self
189 .scale
190 .as_ref()
191 .map(|expr| expr.resolve(ctx))
192 .transpose()?;
193 let grouping_separator = self
194 .grouping_separator
195 .as_ref()
196 .map(|expr| expr.resolve(ctx))
197 .transpose()?;
198 let decimal_separator = self
199 .decimal_separator
200 .map_resolve_with_default(ctx, || DEFAULT_DECIMAL_SEPARATOR.clone())?;
201
202 format_number(value, scale, grouping_separator, decimal_separator)
203 }
204
205 fn type_def(&self, _: &state::TypeState) -> TypeDef {
206 TypeDef::bytes().infallible()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::value;
214
215 test_function![
216 format_number => FormatNumber;
217
218 number {
219 args: func_args![value: 1234.567],
220 want: Ok(value!("1234.567")),
221 tdef: TypeDef::bytes().infallible(),
222 }
223
224 precision {
225 args: func_args![value: 1234.567,
226 scale: 2],
227 want: Ok(value!("1234.56")),
228 tdef: TypeDef::bytes().infallible(),
229 }
230
231
232 separator {
233 args: func_args![value: 1234.567,
234 scale: 2,
235 decimal_separator: ","],
236 want: Ok(value!("1234,56")),
237 tdef: TypeDef::bytes().infallible(),
238 }
239
240 more_separators {
241 args: func_args![value: 1234.567,
242 scale: 2,
243 decimal_separator: ",",
244 grouping_separator: " "],
245 want: Ok(value!("1 234,56")),
246 tdef: TypeDef::bytes().infallible(),
247 }
248
249 big_number {
250 args: func_args![value: 11_222_333_444.567_89,
251 scale: 3,
252 decimal_separator: ",",
253 grouping_separator: "."],
254 want: Ok(value!("11.222.333.444,567")),
255 tdef: TypeDef::bytes().infallible(),
256 }
257
258 integer {
259 args: func_args![value: 100.0],
260 want: Ok(value!("100")),
261 tdef: TypeDef::bytes().infallible(),
262 }
263
264 integer_decimals {
265 args: func_args![value: 100.0,
266 scale: 2],
267 want: Ok(value!("100.00")),
268 tdef: TypeDef::bytes().infallible(),
269 }
270
271 float_no_decimals {
272 args: func_args![value: 123.45,
273 scale: 0],
274 want: Ok(value!("123")),
275 tdef: TypeDef::bytes().infallible(),
276 }
277
278 integer_no_decimals {
279 args: func_args![value: 12345,
280 scale: 2],
281 want: Ok(value!("12345.00")),
282 tdef: TypeDef::bytes().infallible(),
283 }
284 ];
285}