1use mf2_catalog::{Catalog, Entry, MsgId, Names, StrRef};
4
5use crate::datetime::TimeZone;
6use crate::error::FormatError;
7use crate::eval::{self, Args, Env, Out};
8use crate::function::Registry;
9use crate::host::Host;
10use crate::parts::{ExpressionPart, FallbackSource, Isolation, MarkupPart, Part, PartSink};
11use crate::scratch::Scratch;
12use crate::sink::{ErrorSink, Sink};
13use crate::value::Arg;
14
15#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
18#[non_exhaustive]
19pub enum BidiStrategy {
20 #[default]
22 Default,
23 None,
25}
26
27#[non_exhaustive]
30pub struct FormatContext {
31 pub bidi: BidiStrategy,
33 pub host: &'static dyn Host,
35 pub time_zone: TimeZone,
38}
39
40impl FormatContext {
41 pub const fn new(host: &'static dyn Host) -> FormatContext {
43 FormatContext {
44 bidi: BidiStrategy::Default,
45 host,
46 time_zone: TimeZone::UTC,
47 }
48 }
49}
50
51impl core::fmt::Debug for FormatContext {
52 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53 f.debug_struct("FormatContext")
54 .field("bidi", &self.bidi)
55 .field("time_zone", &self.time_zone)
56 .finish_non_exhaustive()
57 }
58}
59
60#[derive(Clone, Copy)]
62pub struct Formatter<'c> {
63 catalog: &'c Catalog,
64 registry: &'c Registry,
65 cx: &'c FormatContext,
66}
67
68impl core::fmt::Debug for Formatter<'_> {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 f.debug_struct("Formatter")
71 .field("catalog", self.catalog)
72 .finish_non_exhaustive()
73 }
74}
75
76impl<'c> Formatter<'c> {
77 pub const fn new(catalog: &'c Catalog, registry: &'c Registry, cx: &'c FormatContext) -> Self {
79 Formatter {
80 catalog,
81 registry,
82 cx,
83 }
84 }
85
86 pub fn catalog(&self) -> &'c Catalog {
88 self.catalog
89 }
90
91 #[inline]
94 pub fn simple(&self, id: MsgId) -> Option<&'c str> {
95 match self.catalog.get(id) {
96 Entry::Simple(r) => self.catalog.text(r),
97 _ => None,
98 }
99 }
100
101 #[inline]
104 #[doc(hidden)]
105 pub fn simple_ref(&self, id: MsgId) -> Option<StrRef> {
106 match self.catalog.get(id) {
107 Entry::Simple(r) => Some(r),
108 _ => None,
109 }
110 }
111
112 pub fn write(&self, id: MsgId, args: &[Arg<'_>], out: &mut dyn Sink, errs: &mut dyn ErrorSink) {
114 self.run(id, Args::Positional(args), &mut StrOut { out }, errs);
115 }
116
117 pub fn parts(
119 &self,
120 id: MsgId,
121 args: &[Arg<'_>],
122 out: &mut dyn PartSink,
123 errs: &mut dyn ErrorSink,
124 ) {
125 self.run(id, Args::Positional(args), &mut PartsOut { out }, errs);
126 }
127
128 pub fn write_named(
131 &self,
132 id: MsgId,
133 args: &[(&str, Arg<'_>)],
134 out: &mut dyn Sink,
135 errs: &mut dyn ErrorSink,
136 ) {
137 let map = self.slot_map(id, args);
138 self.run(id, Args::Named(args, &map), &mut StrOut { out }, errs);
139 }
140
141 pub fn parts_named(
143 &self,
144 id: MsgId,
145 args: &[(&str, Arg<'_>)],
146 out: &mut dyn PartSink,
147 errs: &mut dyn ErrorSink,
148 ) {
149 let map = self.slot_map(id, args);
150 self.run(id, Args::Named(args, &map), &mut PartsOut { out }, errs);
151 }
152
153 fn slot_map(&self, id: MsgId, args: &[(&str, Arg<'_>)]) -> Scratch<u32> {
155 let names = match self.catalog.get(id) {
156 Entry::Pattern(m) | Entry::Select(m) => m.names(),
157 _ => Names::EMPTY,
158 };
159 let mut map = Scratch::new();
160 let nfc = self.catalog.nfc_map();
164 for slot in 0..names.external_count() {
165 let name = names.external(slot).and_then(|r| self.catalog.text(r));
166 let found = name.and_then(|name| {
167 args.iter()
168 .position(|(n, _)| crate::nfc::equivalent(nfc, n, name))
169 });
170 let i = found
171 .and_then(|i| u32::try_from(i).ok())
172 .unwrap_or(u32::MAX);
173 if !map.push(i) {
174 break;
175 }
176 }
177 map
178 }
179
180 fn run(&self, id: MsgId, args: Args<'_, '_>, out: &mut dyn Out, errs: &mut dyn ErrorSink) {
181 match self.catalog.get(id) {
182 Entry::Simple(r) => {
183 if !out.text(self.catalog, r) {
184 errs.error(FormatError::Malformed);
185 let iso = (self.cx.bidi == BidiStrategy::Default).then_some(Isolation::Fsi);
186 out.fallback(FallbackSource::Unknown, iso);
187 }
188 }
189 Entry::Pattern(m) | Entry::Select(m) => {
190 let names = m.names();
191 let args = match args {
195 Args::Positional(a) => {
196 Args::Positional(a.get(..names.external_count() as usize).unwrap_or(a))
197 }
198 named @ Args::Named(..) => named,
199 };
200 let env = Env {
201 catalog: self.catalog,
202 registry: self.registry,
203 host: self.cx.host,
204 bidi: self.cx.bidi,
205 time_zone: &self.cx.time_zone,
206 names,
207 args,
208 };
209 eval::run(&env, m, out, errs);
210 }
211 Entry::Absent => errs.error(FormatError::MissingMessage),
212 }
213 }
214}
215
216struct StrOut<'s> {
218 out: &'s mut dyn Sink,
219}
220
221impl Out for StrOut<'_> {
222 fn text(&mut self, catalog: &Catalog, r: StrRef) -> bool {
223 self.out.push_catalog_text(catalog, r)
224 }
225
226 fn expression(&mut self, part: ExpressionPart<'_>, iso: Option<Isolation>) {
227 if let Some(i) = iso {
228 self.out.push_str(i.as_str());
229 }
230 part.write(self.out);
231 if iso.is_some() {
232 self.out.push_str(Isolation::Pdi.as_str());
233 }
234 }
235
236 fn fallback(&mut self, source: FallbackSource<'_>, iso: Option<Isolation>) {
237 if let Some(i) = iso {
238 self.out.push_str(i.as_str());
239 }
240 self.out.push_str("{");
241 source.write(self.out);
242 self.out.push_str("}");
243 if iso.is_some() {
244 self.out.push_str(Isolation::Pdi.as_str());
245 }
246 }
247
248 fn markup(&mut self, _part: MarkupPart<'_>) {}
249
250 fn wants_markup(&self) -> bool {
251 false
252 }
253}
254
255struct PartsOut<'s> {
257 out: &'s mut dyn PartSink,
258}
259
260impl Out for PartsOut<'_> {
261 fn text(&mut self, catalog: &Catalog, r: StrRef) -> bool {
262 self.out.part_catalog_text(catalog, r)
263 }
264
265 fn expression(&mut self, part: ExpressionPart<'_>, iso: Option<Isolation>) {
266 if let Some(i) = iso {
267 self.out.part(Part::BidiIsolation(i));
268 }
269 self.out.part(Part::Expression(part));
270 if iso.is_some() {
271 self.out.part(Part::BidiIsolation(Isolation::Pdi));
272 }
273 }
274
275 fn fallback(&mut self, source: FallbackSource<'_>, iso: Option<Isolation>) {
276 if let Some(i) = iso {
277 self.out.part(Part::BidiIsolation(i));
278 }
279 self.out.part(Part::Fallback(source));
280 if iso.is_some() {
281 self.out.part(Part::BidiIsolation(Isolation::Pdi));
282 }
283 }
284
285 fn markup(&mut self, part: MarkupPart<'_>) {
286 self.out.part(Part::Markup(part));
287 }
288
289 fn wants_markup(&self) -> bool {
290 true
291 }
292}