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}