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