Skip to main content

mf2_runtime/
value.rs

1//! Arguments and resolved values.
2
3use alloc::boxed::Box;
4use alloc::string::String;
5use core::any::Any;
6
7use crate::datetime::DateTime;
8use crate::host::Host;
9use crate::number::{Measure, Number};
10use crate::parts::FallbackSource;
11
12/// A positional argument: slot `i` of the call site is `args[i]` (the
13/// manifest's slot order). Small on purpose: every variant is a code path in
14/// the wasm. Non-exhaustive, so variants can be added.
15#[derive(Clone, Copy)]
16#[non_exhaustive]
17pub enum Arg<'a> {
18    /// A string.
19    Str(&'a str),
20    /// An integer.
21    Int(i64),
22    /// A float (finite values format; others are a Bad Operand).
23    Float(f64),
24    /// An exact decimal as `number-literal` text (`-12.50`, `1e3`).
25    Decimal(&'a str),
26    /// An application value.
27    Custom(&'a dyn CustomValue),
28    /// No value: Unresolved Variable.
29    Unset,
30    /// A date/time (by reference, so `Arg` stays small).
31    DateTime(&'a DateTime<'a>),
32}
33
34/// As a derived `Debug` would show it, but an application value, which has
35/// no `Debug` of its own, is `Custom(..)`.
36impl core::fmt::Debug for Arg<'_> {
37    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38        match self {
39            Arg::Str(s) => f.debug_tuple("Str").field(s).finish(),
40            Arg::Int(n) => f.debug_tuple("Int").field(n).finish(),
41            Arg::Float(x) => f.debug_tuple("Float").field(x).finish(),
42            Arg::Decimal(s) => f.debug_tuple("Decimal").field(s).finish(),
43            Arg::Custom(_) => f.write_str("Custom(..)"),
44            Arg::Unset => f.write_str("Unset"),
45            Arg::DateTime(d) => f.debug_tuple("DateTime").field(d).finish(),
46        }
47    }
48}
49
50impl<'a> From<&'a DateTime<'a>> for Arg<'a> {
51    #[inline]
52    fn from(d: &'a DateTime<'a>) -> Self {
53        Arg::DateTime(d)
54    }
55}
56
57impl<'a> From<&'a str> for Arg<'a> {
58    #[inline]
59    fn from(s: &'a str) -> Self {
60        Arg::Str(s)
61    }
62}
63
64impl<'a> From<&'a String> for Arg<'a> {
65    #[inline]
66    fn from(s: &'a String) -> Self {
67        Arg::Str(s)
68    }
69}
70
71macro_rules! int_arg {
72    ($($t:ty),*) => {$(
73        impl From<$t> for Arg<'_> {
74            #[inline]
75            fn from(n: $t) -> Self {
76                Arg::Int(i64::from(n))
77            }
78        }
79    )*};
80}
81int_arg!(i8, i16, i32, i64, u8, u16, u32);
82
83impl From<f64> for Arg<'_> {
84    #[inline]
85    fn from(x: f64) -> Self {
86        Arg::Float(x)
87    }
88}
89
90impl From<f32> for Arg<'_> {
91    #[inline]
92    fn from(x: f32) -> Self {
93        Arg::Float(f64::from(x))
94    }
95}
96
97/// An application's own argument type: what it converts to.
98pub trait CustomValue {
99    /// Its string form (unannotated placeholders, `:string`).
100    fn as_str(&self) -> Option<&str> {
101        None
102    }
103
104    /// Its numeric value (numeric operands).
105    fn as_number(&self) -> Option<Number> {
106        None
107    }
108
109    /// Itself, for handlers that know the concrete type.
110    fn as_any(&self) -> Option<&dyn Any> {
111        None
112    }
113
114    /// Its date/time value (date/time operands, `datetime.md`).
115    fn as_date_time(&self) -> Option<DateTime<'_>> {
116        None
117    }
118
119    /// Its number with a currency or a unit (`:currency` and `:unit`
120    /// operands, `number.md`; a numeric operand for the other numeric
121    /// functions).
122    fn as_measure(&self) -> Option<Measure<'_>> {
123        None
124    }
125}
126
127/// A resolved value's data. The handler that resolved it decides how it
128/// formats and selects; a value no handler resolved (a literal, an
129/// argument) is *unannotated*.
130#[non_exhaustive]
131pub enum Value<'a> {
132    /// A string: a literal, a string argument, or `:string`'s operand.
133    Str(&'a str),
134    /// An integer argument.
135    Int(i64),
136    /// A float argument.
137    Float(f64),
138    /// A decimal argument (`number-literal` text).
139    Decimal(&'a str),
140    /// A number and its resolved options: `:number`, `:integer`, `:offset`,
141    /// `:percent`.
142    Number(Number),
143    /// An application value.
144    Custom(&'a dyn CustomValue),
145    /// A custom handler's own data (the only allocation a handler makes).
146    Boxed(Box<dyn Any>),
147    /// A fallback value, as a function's operand: the operand failed to
148    /// resolve (an unresolved variable, a declaration that failed). The
149    /// handler decides — the numeric ones report Bad Operand, `:string`
150    /// takes the text of its representation (`{$x}`), as the suite expects.
151    Fallback(FallbackSource<'a>),
152    /// A date/time: an argument, or what `:datetime`, `:date`, `:time`
153    /// resolved (with its options).
154    DateTime(DateTime<'a>),
155    /// A number with a currency or a unit: what `:currency` and `:unit`
156    /// resolved.
157    Measure(Measure<'a>),
158}
159
160/// As a derived `Debug` would show it, but an application value and a
161/// handler's own data, which have no `Debug` of their own, are `Custom(..)`
162/// and `Boxed(..)`.
163impl core::fmt::Debug for Value<'_> {
164    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
165        match self {
166            Value::Str(s) => f.debug_tuple("Str").field(s).finish(),
167            Value::Int(n) => f.debug_tuple("Int").field(n).finish(),
168            Value::Float(x) => f.debug_tuple("Float").field(x).finish(),
169            Value::Decimal(s) => f.debug_tuple("Decimal").field(s).finish(),
170            Value::Number(n) => f.debug_tuple("Number").field(n).finish(),
171            Value::Custom(_) => f.write_str("Custom(..)"),
172            Value::Boxed(_) => f.write_str("Boxed(..)"),
173            Value::Fallback(s) => f.debug_tuple("Fallback").field(s).finish(),
174            Value::DateTime(d) => f.debug_tuple("DateTime").field(d).finish(),
175            Value::Measure(m) => f.debug_tuple("Measure").field(m).finish(),
176        }
177    }
178}
179
180impl<'a> Value<'a> {
181    /// The value of an argument; `None` for [`Arg::Unset`].
182    pub fn from_arg(arg: Arg<'a>) -> Option<Value<'a>> {
183        Some(match arg {
184            Arg::Str(s) => Value::Str(s),
185            Arg::Int(n) => Value::Int(n),
186            Arg::Float(x) => Value::Float(x),
187            Arg::Decimal(s) => Value::Decimal(s),
188            Arg::Custom(c) => Value::Custom(c),
189            Arg::DateTime(d) => Value::DateTime(*d),
190            Arg::Unset => return None,
191        })
192    }
193
194    /// Its string form when it has one without formatting: a string, a
195    /// decimal's text, or an application value's `as_str`.
196    pub fn as_str(&self) -> Option<&str> {
197        match self {
198            Value::Str(s) | Value::Decimal(s) => Some(s),
199            Value::Custom(c) => c.as_str(),
200            _ => None,
201        }
202    }
203
204    /// Its numeric value under the numeric-operand rules (`number.md`,
205    /// "Numeric Operands"): a string or decimal matching `number-literal`,
206    /// an integer, a finite float, a number or a measure (its value, without
207    /// options), an application value's `as_number` (else its measure's).
208    pub fn to_number(&self, host: &dyn Host) -> Option<Number> {
209        match self {
210            Value::Str(s) | Value::Decimal(s) => Number::parse(s),
211            Value::Int(n) => Some(Number::from_i64(*n)),
212            Value::Float(x) => Number::from_f64(*x, host),
213            Value::Number(n) => Some(n.bare()),
214            Value::Measure(m) => Some(m.number.bare()),
215            Value::Custom(c) => c
216                .as_number()
217                .or_else(|| c.as_measure().map(|m| m.number.bare())),
218            Value::Boxed(_) | Value::Fallback(_) | Value::DateTime(_) => None,
219        }
220    }
221
222    /// Its value as a *digit size option* (`number.md`): an integer 0–99 —
223    /// an integer, an integral float, a number, or a string of one or two
224    /// digits without a leading zero. For the function crates' own options
225    /// of that type (`fractionDigits`).
226    pub fn digit_size(&self) -> Option<u8> {
227        crate::number::digit_size(self)
228    }
229
230    /// The concrete value, when it is a `T`: a [`Value::Boxed`], or an
231    /// application value whose `as_any` gives one.
232    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
233        match self {
234            Value::Boxed(b) => b.downcast_ref(),
235            Value::Custom(c) => c.as_any()?.downcast_ref(),
236            _ => None,
237        }
238    }
239
240    /// A copy, for the values `:string` takes: every variant but
241    /// [`Value::Boxed`], [`Value::DateTime`] and [`Value::Measure`].
242    pub(crate) fn try_copy(&self) -> Option<Value<'a>> {
243        Some(match self {
244            Value::Str(s) => Value::Str(s),
245            Value::Int(n) => Value::Int(*n),
246            Value::Float(x) => Value::Float(*x),
247            Value::Decimal(s) => Value::Decimal(s),
248            Value::Number(n) => Value::Number(n.clone()),
249            Value::Custom(c) => Value::Custom(*c),
250            Value::Fallback(f) => Value::Fallback(*f),
251            // No string form (`:string`): Bad Operand.
252            Value::Boxed(_) | Value::DateTime(_) | Value::Measure(_) => return None,
253        })
254    }
255}