derive_deftly_macros/
concat.rs

1//! Implementation of string concatenation `${concat }`
2
3use super::framework::*;
4
5/// Accumulator for string concatenation `${concat }`
6///
7/// Implements [`ExpansionOutput`] and [`SubstParseContext`]:
8/// i.e., it corresponds to the lexical context for a `${concat }`,
9/// and collects the string to be output.
10#[derive(Debug)]
11pub struct Accumulator {
12    def_span: Span,
13    out_span: Option<Span>,
14    text: String,
15    errors: ErrorAccumulator,
16}
17
18impl Accumulator {
19    pub fn finish_literal(self) -> syn::Result<syn::LitStr> {
20        let out_span = self.out_span.unwrap_or(self.def_span);
21        let text = self.errors.finish_with(self.text)?;
22        let lit = syn::LitStr::new(&text, out_span);
23        Ok(lit)
24    }
25
26    pub fn finish_onto<O>(self, np: &O::NotInPaste, out: &mut O)
27    where
28        O: ExpansionOutput,
29    {
30        // We *would* support ${paste ${concat ...}} but it would be weird.
31        let _: O::NotInPaste = *np;
32
33        (|| {
34            let lit = self.finish_literal()?;
35            out.append_syn_litstr(&lit);
36            Ok(())
37        })()
38        .unwrap_or_else(|err| out.record_error(err))
39    }
40
41    fn append_display(&mut self, span: Span, plain: impl Display + Spanned) {
42        self.out_span.get_or_insert(span);
43        write!(&mut self.text, "{}", plain).expect("Display onto String")
44    }
45
46    /// Convert a type to a "nice" string
47    ///
48    /// `syn::Type as ToTokens` produces a lot of `Spacing::Alone`
49    /// which results in a lot of excessive whitespace.
50    ///
51    /// We use an algorithm which prints something reasonably pretty
52    /// which we also think is probably parseable
53    /// unless there `None`-delimited groups (which we make visible).
54    ///
55    /// Note that our documentation doesn't promise parseability,
56    /// just readability.
57    ///
58    /// This algorithm *does* obviously produce parseable output
59    /// if the input is just a single identifier token.
60    ///
61    /// ### Rules
62    ///
63    ///  * We never put spaces next to any of `<>:`
64    ///  * We don't put a space after `&` or `'`
65    ///  * We don't put a space before `,`
66    ///  * We put spaces just inside `{ }` iff nonempty
67    ///  * We don't put spaces just inside `( )` or `[ ]` (or `« »`)
68    ///  * Otherwise, we put a space between each pair of TTs
69    fn append_type_like(&mut self, tokens: TokenStream) {
70        recurse_display_type_like(
71            &mut self.text,
72            &mut Prev::NeverSpace,
73            tokens,
74        )
75        .expect("formatting type-like tokens into String failed");
76    }
77}
78
79/// State (previous token) for `recurse_display_type_like`
80enum Prev {
81    /// Previous token wants to suppress any following space
82    NeverSpace,
83    /// We're at the start of a `{ }` group
84    OpenBrace,
85    /// Insert a space unless the *next* token wants to suppress it.
86    Other,
87}
88
89/// Implementation of `append_type_like`
90fn recurse_display_type_like(
91    out: &mut String,
92    prev: &mut Prev,
93    tokens: TokenStream,
94) -> fmt::Result {
95    for tt in tokens {
96        let def_spc = match &prev {
97            Prev::NeverSpace => "",
98            _other => " ",
99        };
100
101        let wr; // forces every arm to write something
102        let cat; // next value of prev, forces every arm to set it
103        match tt {
104            TT::Punct(p) => {
105                let p = p.as_char();
106                match p {
107                    '<' | '>' | ':' => {
108                        wr = write!(out, "{}", p);
109                        cat = Prev::NeverSpace;
110                    }
111                    '&' | '\'' => {
112                        wr = write!(out, "{}{}", def_spc, p);
113                        cat = Prev::NeverSpace;
114                    }
115                    ',' => {
116                        wr = write!(out, "{}", p);
117                        cat = Prev::Other;
118                    }
119                    other => {
120                        wr = write!(out, "{}{}", def_spc, other);
121                        cat = Prev::Other;
122                    }
123                }
124            }
125            TT::Group(g) => {
126                let delim = g.delimiter();
127                let mut normal = |open, close| {
128                    write!(out, "{}{}", def_spc, open)?;
129                    recurse_display_type_like(
130                        out,
131                        &mut Prev::NeverSpace,
132                        g.stream(),
133                    )?;
134                    write!(out, "{}", close)
135                };
136                match delim {
137                    Delimiter::Brace => {
138                        write!(out, "{}{{", def_spc)?;
139                        let mut inner_prev = Prev::OpenBrace;
140                        recurse_display_type_like(
141                            out,
142                            &mut inner_prev,
143                            g.stream(),
144                        )?;
145                        wr = match inner_prev {
146                            Prev::OpenBrace => write!(out, "}}"),
147                            _other => write!(out, " }}"),
148                        };
149                        cat = Prev::Other;
150                    }
151                    Delimiter::Parenthesis => {
152                        wr = normal('(', ')');
153                        cat = Prev::Other;
154                    }
155                    Delimiter::Bracket => {
156                        wr = normal('[', ']');
157                        cat = Prev::Other;
158                    }
159                    Delimiter::None => {
160                        wr = normal('«', '»');
161                        cat = Prev::Other;
162                    }
163                }
164            }
165            other => {
166                wr = write!(out, "{}{}", def_spc, other);
167                cat = Prev::Other;
168            }
169        }
170
171        let () = wr?;
172        *prev = cat;
173    }
174    Ok(())
175}
176
177impl SubstParseContext for Accumulator {
178    type NotInPaste = ();
179    type NotInConcat = Void;
180    type NotInBool = ();
181    type BoolOnly = Void;
182    type ConcatOnly = ();
183    type DbgContent = Template<Self>;
184    type SpecialParseContext = ();
185
186    fn not_in_paste(_: &impl Spanned) -> syn::Result<()> {
187        Ok(())
188    }
189    fn not_in_concat(span: &impl Spanned) -> syn::Result<Void> {
190        Err(span.error("not allowed in within ${concat ...}"))
191    }
192    fn not_in_bool(_: &impl Spanned) -> syn::Result<()> {
193        Ok(())
194    }
195    fn concat_only(_: &impl Spanned) -> syn::Result<()> {
196        Ok(())
197    }
198}
199
200impl ExpansionOutput for Accumulator {
201    fn append_identfrag_toks<I: IdentFrag>(
202        &mut self,
203        ident: &I,
204    ) -> Result<(), I::BadIdent> {
205        self.append_display(ident.span(), ident.fragment());
206        Ok(())
207    }
208    fn append_idpath<A, B, I>(
209        &mut self,
210        _te_span: Span,
211        pre: A,
212        ident: &I,
213        post: B,
214        _grouping: Grouping,
215    ) -> Result<(), I::BadIdent>
216    where
217        A: FnOnce(&mut TokenAccumulator),
218        B: FnOnce(&mut TokenAccumulator),
219        I: IdentFrag,
220    {
221        match (|| {
222            let mut ta = TokenAccumulator::new();
223            pre(&mut ta);
224            ta.with_tokens(|ts| ident.frag_to_tokens(ts));
225            post(&mut ta);
226            ta.tokens()
227        })() {
228            Ok(y) => self.append_type_like(y),
229            Err(e) => self.errors.push(e),
230        }
231        Ok(())
232    }
233    fn append_syn_litstr(&mut self, lit: &syn::LitStr) {
234        if lit.suffix() != "" {
235            self.errors
236                .push(lit.error("string suffix forbidden in ${concat }"));
237            return;
238        }
239        self.append_display(lit.span(), lit.value());
240    }
241    fn append_syn_type(
242        &mut self,
243        te_span: Span,
244        v: syn::Type,
245        grouping: Grouping,
246    ) {
247        self.append_syn_type_inner(te_span, v, grouping);
248    }
249    fn append_syn_type_inner(
250        &mut self,
251        _te_span: Span,
252        ty: syn::Type,
253        _grouping: Grouping,
254    ) {
255        self.append_type_like(ty.to_token_stream());
256    }
257    fn dbg_expand<'c>(
258        &mut self,
259        kw_span: Span,
260        ctx: GeneralContext<'c>,
261        msg: &mut String,
262        content: &Template<Accumulator>,
263    ) -> fmt::Result {
264        let mut child = Accumulator::new_with_span(kw_span);
265        content.expand(ctx, &mut child);
266        match child.errors.examine() {
267            Some(e) => write!(msg, "/* ERROR: {} */", e)?,
268            None => write!(msg, "{}", child.text)?,
269        }
270        child.finish_onto(&(), self);
271        Ok(())
272    }
273
274    fn new_with_span(kw_span: Span) -> Self {
275        Accumulator {
276            def_span: kw_span,
277            out_span: None,
278            text: String::new(),
279            errors: ErrorAccumulator::default(),
280        }
281    }
282    fn ignore_impl(self) -> syn::Result<()> {
283        self.errors.finish()
284    }
285
286    fn append_tokens_with(
287        &mut self,
288        (_not_in_paste, not_in_concat): &((), Void),
289        _: impl FnOnce(&mut TokenAccumulator) -> syn::Result<()>,
290    ) -> syn::Result<()> {
291        void::unreachable(*not_in_concat)
292    }
293
294    fn append_bool_only(&mut self, bool_only: &Self::BoolOnly) -> ! {
295        void::unreachable(*bool_only)
296    }
297
298    fn record_error(&mut self, err: syn::Error) {
299        self.errors.push(err);
300    }
301}
302
303impl Expand<Accumulator> for TemplateElement<Accumulator> {
304    fn expand<'c>(
305        &self,
306        ctx: GeneralContext<'c>,
307        out: &mut Accumulator,
308    ) -> syn::Result<()> {
309        match self {
310            TE::Ident(_ident, not_in_concat) => {
311                // We *would* support ${concat ident}
312                // but it would be confusing.
313                void::unreachable(*not_in_concat);
314            }
315            TE::LitStr(lit) => out.append_syn_litstr(&lit),
316            TE::Subst(e) => e.expand(ctx, out)?,
317            TE::Repeat(e) => e.expand(ctx, out),
318            TE::Literal(_, allow_tokens)
319            | TE::Punct(_, allow_tokens)
320            | TE::Group { allow_tokens, .. } => {
321                void::unreachable(allow_tokens.1)
322            }
323        }
324        Ok(())
325    }
326}