Skip to main content

mf2_runtime/
value.rs

1//! Arguments and resolved values (`plans/03-runtime.md` §2.3).
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* (`plans/03-runtime.md` §2.6).
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    /// (`plans/03-runtime.md` §2.6).
152    Fallback(FallbackSource<'a>),
153    /// A date/time: an argument, or what `:datetime`, `:date`, `:time`
154    /// resolved (with its options).
155    DateTime(DateTime<'a>),
156    /// A number with a currency or a unit: what `:currency` and `:unit`
157    /// resolved.
158    Measure(Measure<'a>),
159}
160
161/// As a derived `Debug` would show it, but an application value and a
162/// handler's own data, which have no `Debug` of their own, are `Custom(..)`
163/// and `Boxed(..)`.
164impl core::fmt::Debug for Value<'_> {
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        match self {
167            Value::Str(s) => f.debug_tuple("Str").field(s).finish(),
168            Value::Int(n) => f.debug_tuple("Int").field(n).finish(),
169            Value::Float(x) => f.debug_tuple("Float").field(x).finish(),
170            Value::Decimal(s) => f.debug_tuple("Decimal").field(s).finish(),
171            Value::Number(n) => f.debug_tuple("Number").field(n).finish(),
172            Value::Custom(_) => f.write_str("Custom(..)"),
173            Value::Boxed(_) => f.write_str("Boxed(..)"),
174            Value::Fallback(s) => f.debug_tuple("Fallback").field(s).finish(),
175            Value::DateTime(d) => f.debug_tuple("DateTime").field(d).finish(),
176            Value::Measure(m) => f.debug_tuple("Measure").field(m).finish(),
177        }
178    }
179}
180
181impl<'a> Value<'a> {
182    /// The value of an argument; `None` for [`Arg::Unset`].
183    pub fn from_arg(arg: Arg<'a>) -> Option<Value<'a>> {
184        Some(match arg {
185            Arg::Str(s) => Value::Str(s),
186            Arg::Int(n) => Value::Int(n),
187            Arg::Float(x) => Value::Float(x),
188            Arg::Decimal(s) => Value::Decimal(s),
189            Arg::Custom(c) => Value::Custom(c),
190            Arg::DateTime(d) => Value::DateTime(*d),
191            Arg::Unset => return None,
192        })
193    }
194
195    /// Its string form when it has one without formatting: a string, a
196    /// decimal's text, or an application value's `as_str`.
197    pub fn as_str(&self) -> Option<&str> {
198        match self {
199            Value::Str(s) | Value::Decimal(s) => Some(s),
200            Value::Custom(c) => c.as_str(),
201            _ => None,
202        }
203    }
204
205    /// Its numeric value under the numeric-operand rules (`number.md`,
206    /// "Numeric Operands"): a string or decimal matching `number-literal`,
207    /// an integer, a finite float, a number or a measure (its value, without
208    /// options), an application value's `as_number` (else its measure's).
209    pub fn to_number(&self, host: &dyn Host) -> Option<Number> {
210        match self {
211            Value::Str(s) | Value::Decimal(s) => Number::parse(s),
212            Value::Int(n) => Some(Number::from_i64(*n)),
213            Value::Float(x) => Number::from_f64(*x, host),
214            Value::Number(n) => Some(n.bare()),
215            Value::Measure(m) => Some(m.number.bare()),
216            Value::Custom(c) => c
217                .as_number()
218                .or_else(|| c.as_measure().map(|m| m.number.bare())),
219            Value::Boxed(_) | Value::Fallback(_) | Value::DateTime(_) => None,
220        }
221    }
222
223    /// Its value as a *digit size option* (`number.md`): an integer 0–99 —
224    /// an integer, an integral float, a number, or a string of one or two
225    /// digits without a leading zero. For the function crates' own options
226    /// of that type (`fractionDigits`).
227    pub fn digit_size(&self) -> Option<u8> {
228        crate::number::digit_size(self)
229    }
230
231    /// The concrete value, when it is a `T`: a [`Value::Boxed`], or an
232    /// application value whose `as_any` gives one.
233    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
234        match self {
235            Value::Boxed(b) => b.downcast_ref(),
236            Value::Custom(c) => c.as_any()?.downcast_ref(),
237            _ => None,
238        }
239    }
240
241    /// A copy, for the values `:string` takes: every variant but
242    /// [`Value::Boxed`], [`Value::DateTime`] and [`Value::Measure`].
243    pub(crate) fn try_copy(&self) -> Option<Value<'a>> {
244        Some(match self {
245            Value::Str(s) => Value::Str(s),
246            Value::Int(n) => Value::Int(*n),
247            Value::Float(x) => Value::Float(*x),
248            Value::Decimal(s) => Value::Decimal(s),
249            Value::Number(n) => Value::Number(n.clone()),
250            Value::Custom(c) => Value::Custom(*c),
251            Value::Fallback(f) => Value::Fallback(*f),
252            // No string form (`:string`): Bad Operand.
253            Value::Boxed(_) | Value::DateTime(_) | Value::Measure(_) => return None,
254        })
255    }
256}