From f4b981dc7855cd2f43152c106697e850788a89e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sun, 27 Apr 2025 11:11:11 +0200 Subject: [PATCH] xso-proc: allow injecting states into the AsItemsSubmachine This is useful if constant data is to be emitted into the element header and a prerequisite for attribute-switched enums. --- xso-proc/src/state.rs | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/xso-proc/src/state.rs b/xso-proc/src/state.rs index 5a820792ece58c132007c8cf7d7bba81f50d8b1f..bf9de75ac7f1a932737f96660bfc5739a0605b95 100644 --- a/xso-proc/src/state.rs +++ b/xso-proc/src/state.rs @@ -11,6 +11,7 @@ use quote::{quote, ToTokens}; use syn::*; /// A single state in a parser or serializer state machine. +#[derive(Clone)] pub(crate) struct State { /// Name of the state enum variant for this state. name: Ident, @@ -121,10 +122,11 @@ pub(crate) struct FromEventsSubmachine { /// States and state transition implementations. pub(crate) states: Vec, - /// Initializer expression. + /// Initializer snippet. /// - /// This expression must evaluate to a - /// `Result<#state_ty_ident, xso::FromEventsError>`. + /// The structure needed here depends on the + /// [`FromEventsStateMachine::mode`] field of the final state machine this + /// submachine is compiled to or merged into. pub(crate) init: TokenStream, } @@ -312,6 +314,38 @@ impl AsItemsSubmachine { } } + /// Insert a state into the element header state chain. + /// + /// It is not possible to add, remove or access a field from within this + /// state; all data from the first state will be passed through this state + /// unaltered. + /// + /// `name` must be the unambiguous name of the state. + /// + /// `item` must be an expression which generates the `Item` to be emitted + /// from this state. + pub(crate) fn with_extra_header_state(mut self, name: Ident, item: TokenStream) -> Self { + // We always have at least three states: ElementHeadStart, + // ElementHeadEnd, ElementFoot. + assert!(self.states.len() >= 3); + + // We then use the first state after the ElementHeadStart as template. + // It does not really matter which one we use, as long as we do not + // use ElementHeadStart (because that would be before the element + // header) or any state after the ElementHeadEnd. + let mut template = self.states[1].clone(); + // Override all fields but decl and destructure. Those define the data + // which is carried by the State, and we must keep that intact to pass + // the data through the state machine unaltered. + template.name = name; + template.uses_mut = None; + template.advance_body = quote! { ::core::option::Option::Some(#item) }; + + self.states.insert(1, template); + + self + } + /// Update the [`init`][`Self::init`] field in-place. /// /// The function will receive a reference to the current `init` value,