Skip to main content

mf2_runtime/functions/
number.rs

1//! `:number`, `:integer`, `:offset` (`functions/number.md`): the core
2//! numeric semantics of [`crate::number`], in plain digits —
3//! `useGrouping=always` / `min2` report *Unsupported Operation* and format
4//! without grouping. `mf2-fn-number` supplies the handlers that write the
5//! language's own form, under the same names. Where the host formats
6//! numbers (`crate::INTL_NUMBERS`) the text comes from its number
7//! formatter.
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        // plain handlers cannot honour it (`mf2-fn-number`'s do).
46        if matches!(n.grouping(), Some(Grouping::Always | Grouping::Min2)) {
47            errs.error(FormatError::UnsupportedOperation);
48        }
49        Some(Value::Number(n))
50    }
51
52    fn formattable(&self, _cx: &FnContext<'_>, value: &Value<'_>) -> Result<(), FormatError> {
53        match value {
54            Value::Number(_) => Ok(()),
55            _ => Err(FormatError::MessageFunctionError),
56        }
57    }
58
59    fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
60        if let Value::Number(n) = value {
61            n.write_display(cx, out);
62        }
63    }
64
65    fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
66        if let Value::Number(n) = value {
67            n.display_parts(cx, out);
68        }
69    }
70
71    fn part_kind(&self) -> &'static str {
72        "number"
73    }
74
75    fn dir(&self, _cx: &FnContext<'_>, _value: &Value<'_>) -> Dir {
76        // Neutral digits (P4: the locale's numbering system decides).
77        Dir::Ltr
78    }
79
80    fn selectable(&self, value: &Value<'_>) -> bool {
81        matches!(value, Value::Number(n) if n.selectable())
82    }
83
84    fn matches(
85        &self,
86        cx: &FnContext<'_>,
87        value: &Value<'_>,
88        key: &str,
89        errs: &mut dyn ErrorSink,
90    ) -> bool {
91        match value {
92            Value::Number(n) => number::matches(n, cx, key, errs),
93            _ => false,
94        }
95    }
96
97    fn better_than(&self, _cx: &FnContext<'_>, _value: &Value<'_>, key1: &str, key2: &str) -> bool {
98        number::better_than(key1, key2)
99    }
100}
101
102impl core::fmt::Debug for NumberFunction {
103    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        f.write_str("NumberFunction")
105    }
106}