Skip to main content

mf2_runtime/functions/
number.rs

1//! `:number`, `:integer`, `:offset` (`functions/number.md`): the core
2//! numeric semantics of [`crate::number`], with neutral symbols —
3//! `useGrouping=always` / `min2` report *Unsupported Operation* and format
4//! neutrally (`plans/03-runtime.md` §5.1). `mf2-fn-number` supplies the
5//! localized handlers under the same names. With feature `intl` on
6//! `wasm32-unknown-unknown` the neutral text comes from the host's number
7//! formatter (`crate::INTL_NUMBERS`).
8
9use mf2_model::Dir;
10
11use crate::error::FormatError;
12use crate::function::{FnContext, Function, Options};
13use crate::number::{self, Grouping, NumberSpec};
14use crate::sink::{ErrorSink, Sink, SubPartSink};
15use crate::value::Value;
16
17/// A numeric handler: `:number`, `:integer` or `:offset`.
18#[derive(Clone, Copy)]
19pub struct NumberFunction {
20    spec: NumberSpec,
21}
22
23impl NumberFunction {
24    pub(crate) const NUMBER: NumberFunction = NumberFunction {
25        spec: NumberSpec::NUMBER,
26    };
27    pub(crate) const INTEGER: NumberFunction = NumberFunction {
28        spec: NumberSpec::INTEGER,
29    };
30    pub(crate) const OFFSET: NumberFunction = NumberFunction {
31        spec: NumberSpec::OFFSET,
32    };
33}
34
35impl Function for NumberFunction {
36    fn resolve<'a>(
37        &self,
38        cx: &FnContext<'_>,
39        operand: Option<&Value<'a>>,
40        options: &Options<'_, 'a>,
41        errs: &mut dyn ErrorSink,
42    ) -> Option<Value<'a>> {
43        let n = number::resolve(self.spec, cx, operand, *options, errs)?;
44        // Grouping other than `auto`/`never` is only about the locale: the
45        // neutral handlers cannot honour it (plans/03-runtime.md §5.1;
46        // `fn-number` does).
47        if matches!(n.grouping(), Some(Grouping::Always | Grouping::Min2)) {
48            errs.error(FormatError::UnsupportedOperation);
49        }
50        Some(Value::Number(n))
51    }
52
53    fn formattable(&self, _cx: &FnContext<'_>, value: &Value<'_>) -> Result<(), FormatError> {
54        match value {
55            Value::Number(_) => Ok(()),
56            _ => Err(FormatError::MessageFunctionError),
57        }
58    }
59
60    fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
61        if let Value::Number(n) = value {
62            n.write_display(cx, out);
63        }
64    }
65
66    fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
67        if let Value::Number(n) = value {
68            n.display_parts(cx, out);
69        }
70    }
71
72    fn part_kind(&self) -> &'static str {
73        "number"
74    }
75
76    fn dir(&self, _cx: &FnContext<'_>, _value: &Value<'_>) -> Dir {
77        // Neutral digits (P4: the locale's numbering system decides).
78        Dir::Ltr
79    }
80
81    fn selectable(&self, value: &Value<'_>) -> bool {
82        matches!(value, Value::Number(n) if n.selectable())
83    }
84
85    fn matches(
86        &self,
87        cx: &FnContext<'_>,
88        value: &Value<'_>,
89        key: &str,
90        errs: &mut dyn ErrorSink,
91    ) -> bool {
92        match value {
93            Value::Number(n) => number::matches(n, cx, key, errs),
94            _ => false,
95        }
96    }
97
98    fn better_than(&self, _cx: &FnContext<'_>, _value: &Value<'_>, key1: &str, key2: &str) -> bool {
99        number::better_than(key1, key2)
100    }
101}
102
103impl core::fmt::Debug for NumberFunction {
104    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105        f.write_str("NumberFunction")
106    }
107}