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
34impl<'a> From<&'a DateTime<'a>> for Arg<'a> {
35    #[inline]
36    fn from(d: &'a DateTime<'a>) -> Self {
37        Arg::DateTime(d)
38    }
39}
40
41impl<'a> From<&'a str> for Arg<'a> {
42    #[inline]
43    fn from(s: &'a str) -> Self {
44        Arg::Str(s)
45    }
46}
47
48impl<'a> From<&'a String> for Arg<'a> {
49    #[inline]
50    fn from(s: &'a String) -> Self {
51        Arg::Str(s)
52    }
53}
54
55macro_rules! int_arg {
56    ($($t:ty),*) => {$(
57        impl From<$t> for Arg<'_> {
58            #[inline]
59            fn from(n: $t) -> Self {
60                Arg::Int(i64::from(n))
61            }
62        }
63    )*};
64}
65int_arg!(i8, i16, i32, i64, u8, u16, u32);
66
67impl From<f64> for Arg<'_> {
68    #[inline]
69    fn from(x: f64) -> Self {
70        Arg::Float(x)
71    }
72}
73
74impl From<f32> for Arg<'_> {
75    #[inline]
76    fn from(x: f32) -> Self {
77        Arg::Float(f64::from(x))
78    }
79}
80
81/// An application's own argument type: what it converts to.
82pub trait CustomValue {
83    /// Its string form (unannotated placeholders, `:string`).
84    fn as_str(&self) -> Option<&str> {
85        None
86    }
87
88    /// Its numeric value (numeric operands).
89    fn as_number(&self) -> Option<Number> {
90        None
91    }
92
93    /// Itself, for handlers that know the concrete type.
94    fn as_any(&self) -> Option<&dyn Any> {
95        None
96    }
97
98    /// Its date/time value (date/time operands, `datetime.md`).
99    fn as_date_time(&self) -> Option<DateTime<'_>> {
100        None
101    }
102
103    /// Its number with a currency or a unit (`:currency` and `:unit`
104    /// operands, `number.md`; a numeric operand for the other numeric
105    /// functions).
106    fn as_measure(&self) -> Option<Measure<'_>> {
107        None
108    }
109}
110
111/// A resolved value's data. The handler that resolved it decides how it
112/// formats and selects; a value no handler resolved (a literal, an
113/// argument) is *unannotated* (`plans/03-runtime.md` §2.6).
114#[non_exhaustive]
115pub enum Value<'a> {
116    /// A string: a literal, a string argument, or `:string`'s operand.
117    Str(&'a str),
118    /// An integer argument.
119    Int(i64),
120    /// A float argument.
121    Float(f64),
122    /// A decimal argument (`number-literal` text).
123    Decimal(&'a str),
124    /// A number and its resolved options: `:number`, `:integer`, `:offset`,
125    /// `:percent`.
126    Number(Number),
127    /// An application value.
128    Custom(&'a dyn CustomValue),
129    /// A custom handler's own data (the only allocation a handler makes).
130    Boxed(Box<dyn Any>),
131    /// A fallback value, as a function's operand: the operand failed to
132    /// resolve (an unresolved variable, a declaration that failed). The
133    /// handler decides — the numeric ones report Bad Operand, `:string`
134    /// takes the text of its representation (`{$x}`), as the suite expects
135    /// (`plans/03-runtime.md` §2.6).
136    Fallback(FallbackSource<'a>),
137    /// A date/time: an argument, or what `:datetime`, `:date`, `:time`
138    /// resolved (with its options).
139    DateTime(DateTime<'a>),
140    /// A number with a currency or a unit: what `:currency` and `:unit`
141    /// resolved.
142    Measure(Measure<'a>),
143}
144
145impl<'a> Value<'a> {
146    /// The value of an argument; `None` for [`Arg::Unset`].
147    pub fn from_arg(arg: Arg<'a>) -> Option<Value<'a>> {
148        Some(match arg {
149            Arg::Str(s) => Value::Str(s),
150            Arg::Int(n) => Value::Int(n),
151            Arg::Float(x) => Value::Float(x),
152            Arg::Decimal(s) => Value::Decimal(s),
153            Arg::Custom(c) => Value::Custom(c),
154            Arg::DateTime(d) => Value::DateTime(*d),
155            Arg::Unset => return None,
156        })
157    }
158
159    /// Its string form when it has one without formatting: a string, a
160    /// decimal's text, or an application value's `as_str`.
161    pub fn as_str(&self) -> Option<&str> {
162        match self {
163            Value::Str(s) | Value::Decimal(s) => Some(s),
164            Value::Custom(c) => c.as_str(),
165            _ => None,
166        }
167    }
168
169    /// Its numeric value under the numeric-operand rules (`number.md`,
170    /// "Numeric Operands"): a string or decimal matching `number-literal`,
171    /// an integer, a finite float, a number or a measure (its value, without
172    /// options), an application value's `as_number` (else its measure's).
173    pub fn to_number(&self, host: &dyn Host) -> Option<Number> {
174        match self {
175            Value::Str(s) | Value::Decimal(s) => Number::parse(s),
176            Value::Int(n) => Some(Number::from_i64(*n)),
177            Value::Float(x) => Number::from_f64(*x, host),
178            Value::Number(n) => Some(n.bare()),
179            Value::Measure(m) => Some(m.number.bare()),
180            Value::Custom(c) => c
181                .as_number()
182                .or_else(|| c.as_measure().map(|m| m.number.bare())),
183            Value::Boxed(_) | Value::Fallback(_) | Value::DateTime(_) => None,
184        }
185    }
186
187    /// Its value as a *digit size option* (`number.md`): an integer 0–99 —
188    /// an integer, an integral float, a number, or a string of one or two
189    /// digits without a leading zero. For the function crates' own options
190    /// of that type (`fractionDigits`).
191    pub fn digit_size(&self) -> Option<u8> {
192        crate::number::digit_size(self)
193    }
194
195    /// The concrete value, when it is a `T`: a [`Value::Boxed`], or an
196    /// application value whose `as_any` gives one.
197    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
198        match self {
199            Value::Boxed(b) => b.downcast_ref(),
200            Value::Custom(c) => c.as_any()?.downcast_ref(),
201            _ => None,
202        }
203    }
204
205    /// A copy, for the values `:string` takes: every variant but
206    /// [`Value::Boxed`], [`Value::DateTime`] and [`Value::Measure`].
207    pub(crate) fn try_copy(&self) -> Option<Value<'a>> {
208        Some(match self {
209            Value::Str(s) => Value::Str(s),
210            Value::Int(n) => Value::Int(*n),
211            Value::Float(x) => Value::Float(*x),
212            Value::Decimal(s) => Value::Decimal(s),
213            Value::Number(n) => Value::Number(n.clone()),
214            Value::Custom(c) => Value::Custom(*c),
215            Value::Fallback(f) => Value::Fallback(*f),
216            // No string form (`:string`): Bad Operand.
217            Value::Boxed(_) | Value::DateTime(_) | Value::Measure(_) => return None,
218        })
219    }
220}