1#![warn(missing_docs)]
38#![cfg_attr(docsrs, feature(doc_cfg))]
40#![no_std]
41#![forbid(unsafe_code)]
42#![deny(
43 clippy::unwrap_used,
44 clippy::expect_used,
45 clippy::indexing_slicing,
46 clippy::panic
47)]
48
49mod intl;
50mod localize;
51mod measure;
52
53pub use measure::{CURRENCY, CurrencyFunction, UNIT, UnitFunction};
54
55use mf2_runtime::{
56 Dir, ErrorSink, FnContext, FormatError, Function, INTL_NUMBERS, Number, NumberOut, NumberSpec,
57 Options, Sink, SubPartSink, Value,
58};
59
60use localize::{Layout, Out};
61
62#[derive(Clone, Copy)]
65pub struct NumberFunction {
66 spec: NumberSpec,
67 layout: Layout,
68}
69
70impl NumberFunction {
71 const fn new(spec: NumberSpec, layout: Layout) -> Self {
72 NumberFunction { spec, layout }
73 }
74}
75
76pub static NUMBER: NumberFunction = NumberFunction::new(NumberSpec::NUMBER, Layout::Decimal);
78
79pub static INTEGER: NumberFunction = NumberFunction::new(NumberSpec::INTEGER, Layout::Decimal);
81
82pub static OFFSET: NumberFunction = NumberFunction::new(NumberSpec::OFFSET, Layout::Decimal);
84
85pub static PERCENT: NumberFunction = NumberFunction::new(NumberSpec::PERCENT, Layout::Percent);
88
89impl Function for NumberFunction {
90 fn resolve<'a>(
91 &self,
92 cx: &FnContext<'_>,
93 operand: Option<&Value<'a>>,
94 options: &Options<'_, 'a>,
95 errs: &mut dyn ErrorSink,
96 ) -> Option<Value<'a>> {
97 Number::resolve(self.spec, cx, operand, options, errs).map(Value::Number)
98 }
99
100 fn formattable(&self, _cx: &FnContext<'_>, value: &Value<'_>) -> Result<(), FormatError> {
101 match value {
102 Value::Number(_) => Ok(()),
103 _ => Err(FormatError::MessageFunctionError),
104 }
105 }
106
107 fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
108 if INTL_NUMBERS {
109 return intl::format(cx, value, self.layout, NumberOut::Text(out));
110 }
111 if let Value::Number(n) = value
112 && let Some(d) = n.digits()
113 {
114 localize::write(
115 cx.catalog(),
116 &d,
117 n.grouping(),
118 self.layout,
119 &mut Out::Text(out),
120 );
121 }
122 }
123
124 fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
125 if INTL_NUMBERS {
126 return intl::format(cx, value, self.layout, NumberOut::Parts(out));
127 }
128 if let Value::Number(n) = value
129 && let Some(d) = n.digits()
130 {
131 localize::write(
132 cx.catalog(),
133 &d,
134 n.grouping(),
135 self.layout,
136 &mut Out::Parts(out),
137 );
138 }
139 }
140
141 fn part_kind(&self) -> &'static str {
142 "number"
143 }
144
145 fn dir(&self, _cx: &FnContext<'_>, _value: &Value<'_>) -> Dir {
146 Dir::Ltr
148 }
149
150 fn selectable(&self, value: &Value<'_>) -> bool {
151 matches!(value, Value::Number(n) if n.is_selectable())
152 }
153
154 fn matches(
155 &self,
156 cx: &FnContext<'_>,
157 value: &Value<'_>,
158 key: &str,
159 errs: &mut dyn ErrorSink,
160 ) -> bool {
161 match value {
162 Value::Number(n) => n.matches(cx, key, errs),
163 _ => false,
164 }
165 }
166
167 fn better_than(&self, _cx: &FnContext<'_>, _value: &Value<'_>, key1: &str, key2: &str) -> bool {
168 Number::better_than(key1, key2)
169 }
170}
171
172impl core::fmt::Debug for NumberFunction {
173 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
174 f.write_str("NumberFunction")
175 }
176}
177
178#[derive(Clone, Copy, Default, Debug)]
184pub struct Unannotated;
185
186pub static NUMBERS: Unannotated = Unannotated;
188
189impl Function for Unannotated {
190 fn resolve<'a>(
191 &self,
192 _cx: &FnContext<'_>,
193 _operand: Option<&Value<'a>>,
194 _options: &Options<'_, 'a>,
195 errs: &mut dyn ErrorSink,
196 ) -> Option<Value<'a>> {
197 errs.error(FormatError::MessageFunctionError);
199 None
200 }
201
202 fn format(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn Sink) {
203 if INTL_NUMBERS {
204 return intl::format_unannotated(cx, value, NumberOut::Text(out));
205 }
206 if let Some(n) = value.to_number(cx.host()) {
207 localize::write(
208 cx.catalog(),
209 &n.exact_digits(),
210 None,
211 Layout::Decimal,
212 &mut Out::Text(out),
213 );
214 }
215 }
216
217 fn format_parts(&self, cx: &FnContext<'_>, value: &Value<'_>, out: &mut dyn SubPartSink) {
218 if INTL_NUMBERS {
219 return intl::format_unannotated(cx, value, NumberOut::Parts(out));
220 }
221 if let Some(n) = value.to_number(cx.host()) {
222 localize::write(
223 cx.catalog(),
224 &n.exact_digits(),
225 None,
226 Layout::Decimal,
227 &mut Out::Parts(out),
228 );
229 }
230 }
231
232 fn part_kind(&self) -> &'static str {
233 "number"
234 }
235
236 fn dir(&self, _cx: &FnContext<'_>, _value: &Value<'_>) -> Dir {
237 Dir::Ltr
238 }
239}