Skip to main content

xso_proc/
state.rs

1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! State machines for parsing and serialising of structs and enums.
8
9use proc_macro2::TokenStream;
10use quote::{quote, ToTokens};
11use syn::*;
12
13use crate::common::GenericsInfo;
14
15/// A single state in a parser or serializer state machine.
16#[derive(Clone)]
17pub(crate) struct State {
18    /// Name of the state enum variant for this state.
19    name: Ident,
20
21    /// Declaration of members of the state enum in this state.
22    decl: TokenStream,
23
24    /// Destructuring of members of the state enum in this state.
25    destructure: TokenStream,
26
27    /// Right-hand-side of the match arm for this state.
28    advance_body: TokenStream,
29
30    /// If set, that identifier will be bound mutably.
31    uses_mut: Option<Ident>,
32}
33
34impl State {
35    /// Create a new state with the a builder data field.
36    ///
37    /// This is a convenience wrapper around `new()` and `add_field()`. This
38    /// wrapper, or its equivalent, **must** be used for states used in
39    /// [`FromEventsStateMachine`] state machines, as those expect that the
40    /// first field is the builder data at render time.
41    pub(crate) fn new_with_builder(
42        name: Ident,
43        builder_data_ident: &Ident,
44        builder_data_ty: &Type,
45    ) -> Self {
46        let mut result = Self::new(name);
47        result.add_field(builder_data_ident, builder_data_ty);
48        result
49    }
50
51    /// Create a new, empty state.
52    ///
53    /// Note that an empty state will generate invalid code. At the very
54    /// least, a body must be added using [`Self::set_impl`] or
55    /// [`Self::with_impl`]. The various state machines may also have
56    /// additional requirements.
57    pub(crate) fn new(name: Ident) -> Self {
58        Self {
59            name,
60            decl: TokenStream::default(),
61            destructure: TokenStream::default(),
62            advance_body: TokenStream::default(),
63            uses_mut: None,
64        }
65    }
66
67    /// Add a field to this state's data.
68    ///
69    /// - `name` is the name under which the data will be accessible in the
70    ///   state's implementation.
71    /// - `ty` must be the data field's type.
72    pub(crate) fn add_field(&mut self, name: &Ident, ty: &Type) {
73        self.decl.extend(quote! { #name: #ty, });
74        self.destructure.extend(quote! { #name, });
75    }
76
77    /// Modify the state to include another field and return the modified
78    /// state.
79    ///
80    /// This is a consume-and-return-style version of [`Self::add_field`].
81    pub(crate) fn with_field(mut self, name: &Ident, ty: &Type) -> Self {
82        self.add_field(name, ty);
83        self
84    }
85
86    /// Set the `advance` implementation of this state.
87    ///
88    /// `body` must be the body of the right hand side of the match arm for
89    /// the `advance` implementation of the state machine.
90    ///
91    /// See [`FromEventsStateMachine::advance_match_arms`] and
92    /// [`AsItemsSubmachine::compile`] for the respective
93    /// requirements on the implementations.
94    pub(crate) fn with_impl(mut self, body: TokenStream) -> Self {
95        self.advance_body = body;
96        self
97    }
98
99    /// Override the current `advance` implementation of this state.
100    ///
101    /// This is an in-place version of [`Self::with_impl`].
102    pub(crate) fn set_impl(&mut self, body: TokenStream) {
103        self.advance_body = body;
104    }
105
106    /// Modify the state to mark the given field as mutable and return the
107    /// modified state.
108    pub(crate) fn with_mut(mut self, ident: &Ident) -> Self {
109        assert!(self.uses_mut.is_none());
110        self.uses_mut = Some(ident.clone());
111        self
112    }
113}
114
115/// A partial [`FromEventsStateMachine`] which only covers the builder for a
116/// single compound.
117///
118/// See [`FromEventsStateMachine`] for more information on the state machines
119/// in general.
120pub(crate) struct FromEventsSubmachine {
121    /// Additional items necessary for the statemachine.
122    pub(crate) defs: TokenStream,
123
124    /// States and state transition implementations.
125    pub(crate) states: Vec<State>,
126
127    /// Initializer snippet.
128    ///
129    /// The structure needed here depends on the
130    /// [`FromEventsStateMachine::mode`] field of the final state machine this
131    /// submachine is compiled to or merged into.
132    pub(crate) init: TokenStream,
133}
134
135impl FromEventsSubmachine {
136    /// Convert a partial state machine into a full state machine.
137    ///
138    /// This converts the abstract [`State`] items into token
139    /// streams for the respective parts of the state machine (the state
140    /// definitions and the match arms), rendering them effectively immutable.
141    pub(crate) fn compile(self) -> FromEventsStateMachine {
142        let mut state_defs = TokenStream::default();
143        let mut advance_match_arms = TokenStream::default();
144
145        for state in self.states {
146            let State {
147                name,
148                decl,
149                destructure,
150                advance_body,
151                uses_mut,
152            } = state;
153
154            state_defs.extend(quote! {
155                #name { #decl },
156            });
157
158            let binding = if let Some(uses_mut) = uses_mut.as_ref() {
159                quote! {
160                    let mut #uses_mut = #uses_mut;
161                }
162            } else {
163                TokenStream::default()
164            };
165
166            // XXX: nasty hack, but works: the first member of the enum always
167            // exists and it always is the builder data, which we always need
168            // mutably available. So we can just prefix the destructuring
169            // token stream with `mut` to make that first member mutable.
170            advance_match_arms.extend(quote! {
171                Self::#name { mut #destructure } => {
172                    #binding
173                    #advance_body
174                }
175            });
176        }
177
178        FromEventsStateMachine {
179            defs: self.defs,
180            state_defs,
181            advance_match_arms,
182            variants: vec![FromEventsEntryPoint { init: self.init }],
183            mode: FromEventsMatchMode::default(),
184            pre_init: TokenStream::default(),
185            fallback: None,
186        }
187    }
188
189    /// Update the [`init`][`Self::init`] field in-place.
190    ///
191    /// The function will receive a reference to the current `init` value,
192    /// allowing to create "wrappers" around that existing code.
193    pub(crate) fn with_augmented_init<F: FnOnce(&TokenStream) -> TokenStream>(
194        mut self,
195        f: F,
196    ) -> Self {
197        let new_init = f(&self.init);
198        self.init = new_init;
199        self
200    }
201}
202
203/// A partial [`AsItemsStateMachine`] which only covers the builder for a
204/// single compound.
205///
206/// See [`AsItemsStateMachine`] for more information on the state machines
207/// in general.
208pub(crate) struct AsItemsSubmachine {
209    /// Additional items necessary for the statemachine.
210    pub(crate) defs: TokenStream,
211
212    /// States and state transition implementations.
213    pub(crate) states: Vec<State>,
214
215    /// A pattern match which destructures the target type into its parts, for
216    /// use by `init`.
217    pub(crate) destructure: TokenStream,
218
219    /// An expression which uses the names bound in `destructure` to create a
220    /// an instance of the state enum.
221    ///
222    /// The state enum type is available as `Self` in that context.
223    pub(crate) init: TokenStream,
224}
225
226impl AsItemsSubmachine {
227    /// Convert a partial state machine into a full state machine.
228    ///
229    /// This converts the abstract [`State`] items into token
230    /// streams for the respective parts of the state machine (the state
231    /// definitions and the match arms), rendering them effectively immutable.
232    ///
233    /// This requires that the [`State::advance_body`] token streams evaluate
234    /// to an `Option<Item>`. If it evaluates to `Some(.)`, that is
235    /// emitted from the iterator. If it evaluates to `None`, the `advance`
236    /// implementation is called again.
237    ///
238    /// Each state implementation is augmented to also enter the next state,
239    /// causing the iterator to terminate eventually.
240    pub(crate) fn compile(self) -> AsItemsStateMachine {
241        let mut state_defs = TokenStream::default();
242        let mut advance_match_arms = TokenStream::default();
243
244        for (i, state) in self.states.iter().enumerate() {
245            let State {
246                ref name,
247                ref decl,
248                ref destructure,
249                ref advance_body,
250                ref uses_mut,
251            } = state;
252
253            let footer = match self.states.get(i + 1) {
254                Some(State {
255                    name: ref next_name,
256                    destructure: ref construct_next,
257                    ..
258                }) => {
259                    quote! {
260                        ::core::result::Result::Ok((::core::option::Option::Some(Self::#next_name { #construct_next }), item))
261                    }
262                }
263                // final state -> exit the state machine
264                None => {
265                    quote! {
266                        ::core::result::Result::Ok((::core::option::Option::None, item))
267                    }
268                }
269            };
270
271            state_defs.extend(quote! {
272                #name { #decl },
273            });
274
275            if let Some(uses_mut) = uses_mut.as_ref() {
276                // the variant is non-consuming, meaning it can be called
277                // multiple times and it uses the identifier in `uses_mut`
278                // mutably.
279                // the transition is only triggered when it emits a None
280                // item
281                // (we cannot do this at the place the `State` is constructed,
282                // because we don't yet know all its fields then; it must be
283                // done here.)
284                advance_match_arms.extend(quote! {
285                    Self::#name { #destructure } => {
286                        let mut #uses_mut = #uses_mut;
287                        match #advance_body {
288                            ::core::option::Option::Some(item) => {
289                                ::core::result::Result::Ok((::core::option::Option::Some(Self::#name { #destructure }), ::core::option::Option::Some(item)))
290                            },
291                            item => { #footer },
292                        }
293                    }
294                });
295            } else {
296                // if the variant is consuming, it can only be called once.
297                // it may or may not emit an event, but the transition is
298                // always triggered
299                advance_match_arms.extend(quote! {
300                    Self::#name { #destructure } => {
301                        let item = #advance_body;
302                        #footer
303                    }
304                });
305            }
306        }
307
308        AsItemsStateMachine {
309            defs: self.defs,
310            state_defs,
311            advance_match_arms,
312            variants: vec![AsItemsEntryPoint {
313                init: self.init,
314                destructure: self.destructure,
315            }],
316        }
317    }
318
319    /// Insert a state into the element header state chain.
320    ///
321    /// It is not possible to add, remove or access a field from within this
322    /// state; all data from the first state will be passed through this state
323    /// unaltered.
324    ///
325    /// `name` must be the unambiguous name of the state.
326    ///
327    /// `item` must be an expression which generates the `Item` to be emitted
328    /// from this state.
329    pub(crate) fn with_extra_header_state(mut self, name: Ident, item: TokenStream) -> Self {
330        // We always have at least three states: ElementHeadStart,
331        // ElementHeadEnd, ElementFoot.
332        assert!(self.states.len() >= 3);
333
334        // We then use the first state after the ElementHeadStart as template.
335        // It does not really matter which one we use, as long as we do not
336        // use ElementHeadStart (because that would be before the element
337        // header) or any state after the ElementHeadEnd.
338        let mut template = self.states[1].clone();
339        // Override all fields but decl and destructure. Those define the data
340        // which is carried by the State, and we must keep that intact to pass
341        // the data through the state machine unaltered.
342        template.name = name;
343        template.uses_mut = None;
344        template.advance_body = quote! { ::core::option::Option::Some(#item) };
345
346        self.states.insert(1, template);
347
348        self
349    }
350
351    /// Update the [`init`][`Self::init`] field in-place.
352    ///
353    /// The function will receive a reference to the current `init` value,
354    /// allowing to create "wrappers" around that existing code.
355    pub(crate) fn with_augmented_init<F: FnOnce(&TokenStream) -> TokenStream>(
356        mut self,
357        f: F,
358    ) -> Self {
359        let new_init = f(&self.init);
360        self.init = new_init;
361        self
362    }
363}
364
365/// Container for a single entrypoint into a [`FromEventsStateMachine`].
366pub(crate) struct FromEventsEntryPoint {
367    pub(crate) init: TokenStream,
368}
369
370/// A single variant's entrypoint into the event iterator.
371pub(crate) struct AsItemsEntryPoint {
372    /// A pattern match which destructures the target type into its parts, for
373    /// use by `init`.
374    destructure: TokenStream,
375
376    /// An expression which uses the names bound in `destructure` to create a
377    /// an instance of the state enum.
378    ///
379    /// The state enum type is available as `Self` in that context.
380    init: TokenStream,
381}
382
383/// Control how a state machine attempts to match elements.
384#[derive(Default)]
385pub(crate) enum FromEventsMatchMode {
386    /// Element matching works by chaining the individual submachine's init
387    /// code.
388    ///
389    /// Each submachine has to provide an expression which evaluates to a
390    /// `Result<#state_ty_ident, xso::FromEventsError>` in its
391    /// [`FromEventsSubmachine::init`] code.
392    ///
393    /// If the expression evaluates to
394    /// `Err(FromEventsError::Mismatch { .. })`, the next submachine in merge
395    /// order is tried. If no further submachines exist, the
396    /// [`fallback`][`FromEventsStateMachine::fallback`] code
397    /// is injected (or the mismatch is returned if no fallback code exists.)
398    ///
399    /// This is the default mode.
400    #[default]
401    Chained,
402
403    /// Element matching works with a `match .. { .. }` block.
404    ///
405    /// Each submachine has to provide a match arm (`pattern => result,`)
406    /// snippet in its [`FromEventsSubmachine::init`] code. The `result` must
407    /// evaluate to a `Result<#state_ty_ident, xso::FromEventsError>`.
408    ///
409    /// The statemachine will generate a final arm (`_ => #fallback,`) using
410    /// the [`fallback`][`FromEventsStateMachine::fallback`] code
411    /// if it exists, and a normal `Mismatch` error otherwise.
412    ///
413    /// The `pattern` must be compatible to the `expr` provided here.
414    Matched {
415        /// Expression which the arms match against.
416        expr: TokenStream,
417    },
418}
419
420/// # State machine to implement `xso::FromEventsBuilder`
421///
422/// This struct represents a state machine consisting of the following parts:
423///
424/// - Extra dependencies ([`Self::defs`])
425/// - States ([`Self::state_defs`])
426/// - Transitions ([`Self::advance_match_arms`])
427/// - Entrypoints ([`Self::variants`])
428///
429/// Such a state machine is best constructed by constructing one or
430/// more [`FromEventsSubmachine`] structs and converting/merging them using
431/// `into()` and [`merge`][`Self::merge`].
432///
433/// A state machine has an output type (corresponding to
434/// `xso::FromEventsBuilder::Output`), which is however only implicitly defined
435/// by the expressions generated in the `advance_match_arms`. That means that
436/// merging submachines with different output types works, but will then generate
437/// code which will fail to compile.
438///
439/// When converted to Rust code, the state machine will manifest as (among other
440/// things) an enum type which contains all states and which has an `advance`
441/// method. That method consumes the enum value and returns either a new enum
442/// value, an error, or the output type of the state machine.
443#[derive(Default)]
444pub(crate) struct FromEventsStateMachine {
445    /// Extra items which are needed for the state machine implementation.
446    defs: TokenStream,
447
448    /// Extra code run during pre-init phase.
449    pre_init: TokenStream,
450
451    /// Code to run as fallback if none of the branches matched the start
452    /// event.
453    ///
454    /// If absent, a `FromEventsError::Mismatch` is generated.
455    fallback: Option<TokenStream>,
456
457    /// Configure how the element is matched.
458    ///
459    /// This controls the syntax required in each submachine's
460    /// [`FromEventsSubmachine::init`]. See [`FromEventsMatchMode`] for
461    /// details.
462    mode: FromEventsMatchMode,
463
464    /// A sequence of enum variant declarations, separated and terminated by
465    /// commas.
466    state_defs: TokenStream,
467
468    /// A sequence of `match self { .. }` arms, where `self` is the state
469    /// enumeration type.
470    ///
471    /// Each match arm must either diverge or evaluate to a
472    /// `Result<ControlFlow<State, Output>, xso::error::Error>`, where `State`
473    /// is the state enumeration and `Output` is the state machine's output
474    /// type.
475    advance_match_arms: TokenStream,
476
477    /// The different entrypoints for the state machine.
478    ///
479    /// This may only contain more than one element if an enumeration is being
480    /// constructed by the resulting state machine.
481    variants: Vec<FromEventsEntryPoint>,
482}
483
484impl FromEventsStateMachine {
485    /// Create a new, empty state machine.
486    pub(crate) fn new() -> Self {
487        Self {
488            defs: TokenStream::default(),
489            state_defs: TokenStream::default(),
490            advance_match_arms: TokenStream::default(),
491            pre_init: TokenStream::default(),
492            fallback: None,
493            mode: FromEventsMatchMode::default(),
494            variants: Vec::new(),
495        }
496    }
497
498    /// Merge another state machine into this state machine.
499    ///
500    /// This *discards* the other state machine's pre-init code.
501    pub(crate) fn merge(&mut self, other: FromEventsStateMachine) {
502        assert!(other.fallback.is_none());
503        self.defs.extend(other.defs);
504        self.state_defs.extend(other.state_defs);
505        self.advance_match_arms.extend(other.advance_match_arms);
506        self.variants.extend(other.variants);
507    }
508
509    /// Set additional code to inject at the head of the `new` method for the
510    /// builder.
511    ///
512    /// This can be used to do preliminary checks and is commonly used with
513    /// specifically-formed init codes on the variants.
514    pub(crate) fn set_pre_init(&mut self, code: TokenStream) {
515        self.pre_init = code;
516    }
517
518    /// Set the fallback code to use if none of the branches matches the start
519    /// event.
520    ///
521    /// By default, a `FromEventsError::Mismatch` is generated.
522    pub(crate) fn set_fallback(&mut self, code: TokenStream) {
523        self.fallback = Some(code);
524    }
525
526    /// Set the matching mode for the rendered state machine.
527    ///
528    /// See [`FromEventsMatchMode`] for details.
529    pub(crate) fn set_match_mode(&mut self, mode: FromEventsMatchMode) {
530        self.mode = mode;
531    }
532
533    /// Render the state machine as a token stream.
534    ///
535    /// The token stream contains the following pieces:
536    /// - Any definitions necessary for the statemachine to operate
537    /// - The state enum
538    /// - The builder struct
539    /// - The `xso::FromEventsBuilder` impl on the builder struct
540    /// - A `fn new(rxml::QName, rxml::AttrMap) -> Result<Self>` on the
541    ///   builder struct.
542    pub(crate) fn render(
543        self,
544        vis: &Visibility,
545        generics: &GenericsInfo,
546        builder_ty_ident: &Ident,
547        state_ty_ident: &Ident,
548        output_ty: &Type,
549        validate_fn: Option<&Path>,
550    ) -> Result<TokenStream> {
551        let Self {
552            defs,
553            state_defs,
554            advance_match_arms,
555            variants,
556            pre_init,
557            fallback,
558            mode,
559        } = self;
560
561        let GenericsInfo {
562            decl: ref generics_decl,
563            ref_: ref generics_ref,
564            ref where_clause,
565            ..
566        } = generics;
567
568        let output_ty_ref = make_ty_ref(output_ty);
569
570        let docstr = format!("Build a {0} from XML events.\n\nThis type is generated using the [`macro@xso::FromXml`] derive macro and implements [`xso::FromEventsBuilder`] for {0}.", output_ty_ref);
571
572        let validate_call = match validate_fn {
573            None => quote! {
574                // needed to avoid unused_mut warning.
575                let _ = &mut value;
576            },
577            Some(validate_fn) => {
578                quote! {
579                    #validate_fn(&mut value)?;
580                }
581            }
582        };
583
584        let fallback = fallback.unwrap_or_else(|| {
585            quote! {
586                ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs })
587            }
588        });
589
590        let new_body = match mode {
591            FromEventsMatchMode::Chained => {
592                let mut init_body = pre_init;
593                for variant in variants {
594                    let FromEventsEntryPoint { init } = variant;
595                    init_body.extend(quote! {
596                        let (name, mut attrs) = match { { let _ = &mut attrs; } #init } {
597                            ::core::result::Result::Ok(v) => return ::core::result::Result::Ok(v),
598                            ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)) => return ::core::result::Result::Err(::xso::error::FromEventsError::Invalid(e)),
599                            ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch { name, attrs }) => (name, attrs),
600                        };
601                    })
602                }
603
604                quote! {
605                    #init_body
606                    { let _ = &mut attrs; }
607                    #fallback
608                }
609            }
610
611            FromEventsMatchMode::Matched { expr } => {
612                let mut match_body = TokenStream::default();
613                for variant in variants {
614                    let FromEventsEntryPoint { init } = variant;
615                    match_body.extend(init);
616                }
617
618                quote! {
619                    #pre_init
620                    match #expr {
621                        #match_body
622                        _ => #fallback,
623                    }
624                }
625            }
626        };
627
628        Ok(quote! {
629            #defs
630
631            enum #state_ty_ident #generics_decl #where_clause {
632                #state_defs
633            }
634
635            impl #generics_decl #state_ty_ident #generics_ref #where_clause {
636                fn advance(mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::ops::ControlFlow<Self, #output_ty>, ::xso::error::Error> {
637                    match self {
638                        #advance_match_arms
639                    }.and_then(|__ok| {
640                        match __ok {
641                            ::core::ops::ControlFlow::Break(st) => ::core::result::Result::Ok(::core::ops::ControlFlow::Break(st)),
642                            ::core::ops::ControlFlow::Continue(result) => {
643                                ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(result))
644                            }
645                        }
646                    })
647                }
648            }
649
650            impl #generics_decl #builder_ty_ident #generics_ref #where_clause {
651                fn new(
652                    name: ::xso::exports::rxml::QName,
653                    attrs: ::xso::exports::rxml::AttrMap,
654                    ctx: &::xso::Context<'_>,
655                ) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
656                    #state_ty_ident::new(name, attrs, ctx).map(|ok| Self(::core::option::Option::Some(ok)))
657                }
658            }
659
660            #[doc = #docstr]
661            #[doc(hidden)]
662            #vis struct #builder_ty_ident #generics_decl (::core::option::Option<#state_ty_ident #generics_ref>) #where_clause;
663
664            impl #generics_decl ::xso::FromEventsBuilder for #builder_ty_ident #generics_ref #where_clause {
665                type Output = #output_ty;
666
667                fn feed(&mut self, ev: ::xso::exports::rxml::Event, ctx: &::xso::Context<'_>) -> ::core::result::Result<::core::option::Option<Self::Output>, ::xso::error::Error> {
668                    let inner = self.0.take().expect("feed called after completion");
669                    match inner.advance(ev, ctx)? {
670                        ::core::ops::ControlFlow::Continue(mut value) => {
671                            #validate_call
672                            ::core::result::Result::Ok(::core::option::Option::Some(value))
673                        }
674                        ::core::ops::ControlFlow::Break(st) => {
675                            self.0 = ::core::option::Option::Some(st);
676                            ::core::result::Result::Ok(::core::option::Option::None)
677                        }
678                    }
679                }
680            }
681
682            impl #generics_decl #state_ty_ident #generics_ref #where_clause {
683                fn new(
684                    name: ::xso::exports::rxml::QName,
685                    mut attrs: ::xso::exports::rxml::AttrMap,
686                    ctx: &::xso::Context<'_>,
687                ) -> ::core::result::Result<Self, ::xso::error::FromEventsError> {
688                    #new_body
689                }
690            }
691        })
692    }
693}
694
695/// # State machine to implement an `Iterator<Item = rxml::Event>`.
696///
697/// This struct represents a state machine consisting of the following parts:
698///
699/// - Extra dependencies ([`Self::defs`])
700/// - States ([`Self::state_defs`])
701/// - Transitions ([`Self::advance_match_arms`])
702/// - Entrypoints ([`Self::variants`])
703///
704/// Such a state machine is best constructed by constructing one or
705/// more [`FromEventsSubmachine`] structs and converting/merging them using
706/// `into()` and [`merge`][`Self::merge`].
707///
708/// A state machine has an output type (corresponding to
709/// `xso::FromEventsBuilder::Output`), which is however only implicitly defined
710/// by the expressions generated in the `advance_match_arms`. That means that
711/// merging submachines with different output types works, but will then generate
712/// code which will fail to compile.
713///
714/// When converted to Rust code, the state machine will manifest as (among other
715/// things) an enum type which contains all states and which has an `advance`
716/// method. That method consumes the enum value and returns either a new enum
717/// value, an error, or the output type of the state machine.
718#[derive(Default)]
719pub(crate) struct AsItemsStateMachine {
720    /// Extra items which are needed for the state machine implementation.
721    defs: TokenStream,
722
723    /// A sequence of enum variant declarations, separated and terminated by
724    /// commas.
725    state_defs: TokenStream,
726
727    /// A sequence of `match self { .. }` arms, where `self` is the state
728    /// enumeration type.
729    ///
730    /// Each match arm must either diverge or evaluate to a
731    /// `Result<(Option<State>, Option<Item>), xso::error::Error>`, where
732    /// where `State` is the state enumeration.
733    ///
734    /// If `Some(.)` is returned for the event, that event is emitted. If
735    /// `None` is returned for the event, the advance implementation is called
736    /// again after switching to the state returned in the `Option<State>`
737    /// field.
738    ///
739    /// If `None` is returned for the `Option<State>`, the iterator
740    /// terminates yielding the `Option<Item>` value directly (even if it is
741    /// `None`). After the iterator has terminated, it yields `None`
742    /// indefinitely.
743    advance_match_arms: TokenStream,
744
745    /// The different entrypoints for the state machine.
746    ///
747    /// This may only contain more than one element if an enumeration is being
748    /// serialised by the resulting state machine.
749    variants: Vec<AsItemsEntryPoint>,
750}
751
752impl AsItemsStateMachine {
753    /// Create a new, empty state machine.
754    pub(crate) fn new() -> Self {
755        Self {
756            defs: TokenStream::default(),
757            state_defs: TokenStream::default(),
758            advance_match_arms: TokenStream::default(),
759            variants: Vec::new(),
760        }
761    }
762
763    /// Merge another state machine into this state machine.
764    pub(crate) fn merge(&mut self, other: AsItemsStateMachine) {
765        self.defs.extend(other.defs);
766        self.state_defs.extend(other.state_defs);
767        self.advance_match_arms.extend(other.advance_match_arms);
768        self.variants.extend(other.variants);
769    }
770
771    /// Render the state machine as a token stream.
772    ///
773    /// The token stream contains the following pieces:
774    /// - Any definitions necessary for the statemachine to operate
775    /// - The state enum
776    /// - The iterator struct
777    /// - The `Iterator` impl on the builder struct
778    /// - A `fn new(T) -> Result<Self>` on the iterator struct.
779    pub(crate) fn render(
780        self,
781        vis: &Visibility,
782        generics: &GenericsInfo,
783        input_ty_ref: &Type,
784        state_ty_ident: &Ident,
785        item_iter_ty_lifetime: &Lifetime,
786        item_iter_ty_ident: &Ident,
787    ) -> Result<TokenStream> {
788        let Self {
789            defs,
790            state_defs,
791            advance_match_arms,
792            mut variants,
793        } = self;
794
795        let GenericsInfo {
796            decl: ref generics_decl,
797            ref_: ref generics_ref,
798            ref where_clause,
799            ..
800        } = generics;
801
802        let input_ty_ref_text = make_ty_ref(input_ty_ref);
803        let docstr = format!("Convert a {0} into XML events.\n\nThis type is generated using the [`macro@xso::AsXml`] derive macro and implements [`core::iter:Iterator`] for {0}.", input_ty_ref_text);
804
805        let init_body = if variants.len() == 1 {
806            let AsItemsEntryPoint { destructure, init } = variants.remove(0);
807            quote! {
808                {
809                    let #destructure = value;
810                    #init
811                }
812            }
813        } else {
814            let mut match_arms = TokenStream::default();
815            for AsItemsEntryPoint { destructure, init } in variants {
816                match_arms.extend(quote! {
817                    #destructure => { #init }
818                });
819            }
820
821            quote! {
822                match value {
823                    #match_arms
824                }
825            }
826        };
827
828        Ok(quote! {
829            #defs
830
831            enum #state_ty_ident #generics_decl #where_clause {
832                #state_defs
833            }
834
835            impl #generics_decl #state_ty_ident #generics_ref #where_clause {
836                fn advance(mut self) -> ::core::result::Result<(::core::option::Option<Self>, ::core::option::Option<::xso::Item<#item_iter_ty_lifetime>>), ::xso::error::Error> {
837                    match self {
838                        #advance_match_arms
839                    }
840                }
841
842                fn new(
843                    value: #input_ty_ref,
844                ) -> ::core::result::Result<Self, ::xso::error::Error> {
845                    ::core::result::Result::Ok(#init_body)
846                }
847            }
848
849            #[doc = #docstr]
850            #[doc(hidden)]
851            #vis struct #item_iter_ty_ident #generics_decl (::core::option::Option<#state_ty_ident #generics_ref>) #where_clause;
852
853            impl #generics_decl ::core::iter::Iterator for #item_iter_ty_ident #generics_ref #where_clause {
854                type Item = ::core::result::Result<::xso::Item<#item_iter_ty_lifetime>, ::xso::error::Error>;
855
856                fn next(&mut self) -> ::core::option::Option<Self::Item> {
857                    let mut state = self.0.take()?;
858                    loop {
859                        let (next_state, item) = match state.advance() {
860                            ::core::result::Result::Ok(v) => v,
861                            ::core::result::Result::Err(e) => return ::core::option::Option::Some(::core::result::Result::Err(e)),
862                        };
863                        if let ::core::option::Option::Some(item) = item {
864                            self.0 = next_state;
865                            return ::core::option::Option::Some(::core::result::Result::Ok(item));
866                        }
867                        // no event, do we have a state?
868                        if let ::core::option::Option::Some(st) = next_state {
869                            // we do: try again!
870                            state = st;
871                            continue;
872                        } else {
873                            // we don't: end of iterator!
874                            self.0 = ::core::option::Option::None;
875                            return ::core::option::Option::None;
876                        }
877                    }
878                }
879            }
880
881            impl #generics_decl #item_iter_ty_ident #generics_ref #where_clause {
882                fn new(value: #input_ty_ref) -> ::core::result::Result<Self, ::xso::error::Error> {
883                    #state_ty_ident::new(value).map(|ok| Self(::core::option::Option::Some(ok)))
884                }
885            }
886        })
887    }
888}
889
890/// Construct a path for an intradoc link from a given type.
891fn doc_link_path(ty: &Type) -> Option<String> {
892    match ty {
893        Type::Path(ref ty) => {
894            let (mut buf, offset) = match ty.qself {
895                Some(ref qself) => {
896                    let mut buf = doc_link_path(&qself.ty)?;
897                    buf.push_str("::");
898                    (buf, qself.position)
899                }
900                None => {
901                    let mut buf = String::new();
902                    if ty.path.leading_colon.is_some() {
903                        buf.push_str("::");
904                    }
905                    (buf, 0)
906                }
907            };
908            let last = ty.path.segments.len() - 1;
909            for i in offset..ty.path.segments.len() {
910                let segment = &ty.path.segments[i];
911                buf.push_str(&segment.ident.to_string());
912                if i < last {
913                    buf.push_str("::");
914                }
915            }
916            Some(buf)
917        }
918        Type::Reference(TypeReference { ref elem, .. }) => doc_link_path(elem),
919        _ => None,
920    }
921}
922
923/// Create a markdown snippet which references the given type as cleanly as
924/// possible.
925///
926/// This is used in documentation generation functions.
927///
928/// Not all types can be linked to; those which cannot be linked to will
929/// simply be wrapped in backticks.
930fn make_ty_ref(ty: &Type) -> String {
931    match doc_link_path(ty) {
932        Some(mut path) => {
933            path.reserve(4);
934            path.insert_str(0, "[`");
935            path.push_str("`]");
936            path
937        }
938        None => format!("`{}`", ty.to_token_stream()),
939    }
940}