mf2_runtime/sink.rs
1//! Where formatted text and errors go: minimal traits, not `core::fmt::Write`
2//! (B12). A client supplies its own sink (a DOM or SSR buffer adapter); the
3//! implementations for `String` and `Vec` here grow with `try_reserve` and
4//! drop what does not fit rather than abort.
5
6use alloc::string::String;
7use alloc::vec::Vec;
8
9use mf2_catalog::{Catalog, StrRef};
10
11use crate::error::FormatError;
12
13/// A text sink.
14pub trait Sink {
15 /// Appends `s`.
16 fn push_str(&mut self, s: &str);
17
18 /// Appends the catalog string `r` — the seam for catalog text as JS
19 /// strings (`plans/stretch_goals_after_v1/prob_builtin_strings.md` §7):
20 /// the evaluator writes catalog text only through this method. Returns
21 /// `false` (and writes nothing) when the string is not valid (F4).
22 #[doc(hidden)]
23 fn push_catalog_text(&mut self, catalog: &Catalog, r: StrRef) -> bool {
24 match catalog.text(r) {
25 Some(s) => {
26 self.push_str(s);
27 true
28 }
29 None => false,
30 }
31 }
32}
33
34impl Sink for String {
35 fn push_str(&mut self, s: &str) {
36 // `try_reserve`, then the append behind the test the append makes,
37 // so its infallible-growth branch is dead (see `scratch::try_push`).
38 let room = |t: &String| t.capacity().wrapping_sub(t.len()) >= s.len();
39 if (room(self) || self.try_reserve(s.len()).is_ok()) && room(self) {
40 String::push_str(self, s);
41 }
42 }
43}
44
45/// An error sink.
46pub trait ErrorSink {
47 /// Reports `e`.
48 fn error(&mut self, e: FormatError);
49}
50
51impl ErrorSink for Vec<FormatError> {
52 fn error(&mut self, e: FormatError) {
53 crate::scratch::try_push(self, e);
54 }
55}
56
57/// Discards errors: the release client's policy (`plans/03-runtime.md` §8).
58#[derive(Clone, Copy, Default, Debug)]
59pub struct NoErrors;
60
61impl ErrorSink for NoErrors {
62 #[inline]
63 fn error(&mut self, _: FormatError) {}
64}
65
66/// Receives the sub-parts of a formatted expression (a number's
67/// `minusSign`, `integer`, `decimal`, `fraction`, …).
68pub trait SubPartSink {
69 /// One sub-part: its kind and its text.
70 fn sub_part(&mut self, kind: &str, text: &str);
71}