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}