// XIR attribute parser generator // // Copyright (C) 2014-2022 Ryan Specialty Group, LLC. // // This file is part of TAME. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Attribute parser generator for parsing of [XIRF](super::super::flat). //! //! The parser generator is invoked via the macro //! [`attr_parse!`](crate::attr_parse), //! which expects a `match`-like definition describing the mapping between //! attribute [QNames](crate::xir::QName) and the final value. //! It produces a streaming attribute parser. //! //! All fields recognized by this parser are implicitly optional, //! as this is intended only to extract a grammar from an XML document. //! This destructuring describes the _permissable attributes_ of an element, //! but nothing more. //! Whether or not an attribute is required should be determined by whether //! the produced IR is missing necessary information, //! which is a later lowering operation. //! Further, //! duplicate attributes should be inhibited earlier in the process by XIR, //! if desired. //! //! The parser automatically produces detailed error and diagnostic //! messages for unexpected attributes, //! or attributes that cannot be parsed into the final type. #[macro_export] macro_rules! attr_parse_stream { ($(#[$sattr:meta])* type Object = $objty:ty; type ValueError = $evty:ty; $(vis($vis:vis);)? $(#[$st_attr:meta])? $state_name:ident { $( $(#[$fattr:meta])* $qname:ident => $ty:ty, )* } ) => { paste::paste! { $( // This provides a nice error on $ty itself at the call site, // rather than relying on `Into::into` to cause the error // later on, // which places the error inside the macro definition. assert_impl_all!($ty: TryFrom); )* $(#[$st_attr])? /// #[doc=concat!("Parser producing [`", stringify!($struct_name), "`].")] // TODO: This can be extracted out of the macro. #[derive(Debug, PartialEq, Eq)] $($vis)? enum $state_name { Parsing(crate::xir::QName, crate::xir::OpenSpan), Done(crate::xir::QName, crate::xir::OpenSpan), } /// Intermediate state of parser as fields are aggregated. /// /// TODO: Remove once integrated with `ele_parse!`. #[allow(non_camel_case_types)] #[derive(Debug, PartialEq, Eq, Default)] $($vis)? struct [<$state_name Fields>]; impl crate::xir::parse::AttrParseState for $state_name { type ValueError = $evty; type Fields = [<$state_name Fields>]; fn with_element( ele: crate::xir::QName, span: crate::xir::OpenSpan ) -> Self { Self::Parsing(ele, span) } fn element_name(&self) -> crate::xir::QName { match self { Self::Parsing(qname, _) | Self::Done(qname, _) => *qname, } } fn element_span(&self) -> crate::xir::OpenSpan { match self { Self::Parsing(_, span) | Self::Done(_, span) => *span, } } fn finalize_attr( self, _ctx: &mut ::Context, ) -> Result< Self::Object, crate::xir::parse::AttrParseError, > { unimplemented!() } fn required_missing( &self, #[allow(unused_variables)] // unused if no fields _ctx: &Self::Fields ) -> Vec { unimplemented!() } } impl $state_name { fn done_with_element( ele: crate::xir::QName, span: crate::xir::OpenSpan, ) -> Self { Self::Done(ele, span) } } impl std::fmt::Display for $state_name { /// Additional error context shown in diagnostic messages for /// certain variants of [`ParseError`]. /// /// [`ParseError`]: crate::parse::ParseError fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use crate::fmt::{DisplayWrapper, TtQuote}; use crate::xir::parse::AttrParseState; write!( f, "expecting attributes for element {}", TtQuote::wrap(self.element_name()) ) } } impl crate::parse::ParseState for $state_name { type Token = crate::xir::flat::XirfToken< crate::xir::flat::RefinedText >; type Object = $objty; type Error = crate::xir::parse::AttrParseError; fn parse_token( #[allow(unused_mut)] mut self, tok: Self::Token, ctx: &mut Self::Context, ) -> crate::parse::TransitionResult { use crate::parse::{Transition, Transitionable, ParseStatus}; use crate::xir::{ flat, parse::{AttrParseError, AttrParseState} }; #[allow(unused_imports)] use crate::xir::attr::{Attr, AttrSpan}; // unused if no attrs let ele_name = self.element_name(); match (self, tok) { $( // Use guard so we don't bind as a variable if we // forget to import a const for `$qname`. // We don't use `$qname:pat` because we reuse // `$qname` for error messages. (st @ Self::Parsing(_, _), flat::XirfToken::Attr( attr @ Attr(qn, _, AttrSpan(_kspan, _)) )) if qn == $qname => { match attr.try_into() { Ok(value) => { Transition(st).ok::<$objty>(value) }, Err(e) => Transition(st).err( // Unreachable `Into::into` if // Infallible. #[allow(unreachable_code)] AttrParseError::InvalidValue( Into::<$evty>::into(e), ele_name, ) ), } } )* (st @ Self::Parsing(_, _), flat::XirfToken::Attr(attr)) => { Transition(st).err(AttrParseError::UnexpectedAttr( attr, ele_name, )) }, // Aggregation complete (dead state). (Self::Parsing(ele, span), tok_dead) => { Self::Parsing(ele, span).finalize_attr(ctx) .map(ParseStatus::Object) .transition(Self::done_with_element(ele, span)) .with_lookahead(tok_dead) } // Any tokens received after aggregation is completed // must not be processed, // otherwise we'll recurse indefinitely. (st @ Self::Done(_, _), tok_dead) => { Transition(st).dead(tok_dead) } } } fn is_accepting(&self, _: &Self::Context) -> bool { // All attributes are optional for this parser, // and each token is a complete attribute. true } } } }; } #[cfg(test)] mod test;