tamer: xir::parse: Remove old `attr_parse!` and unused error variants
This cleans up the old implementation now that it's no longer used (as of the previous commit) by `ele_parse!`. It also removes the two error variants that no longer apply: required attributes and duplicate attributes. DEV-13346main
parent
ab0e4151a1
commit
f872181f64
|
@ -23,7 +23,6 @@
|
|||
//! processing of the input XML to ensure well-formedness.
|
||||
|
||||
mod attr;
|
||||
mod attrstream;
|
||||
mod ele;
|
||||
mod error;
|
||||
|
||||
|
|
|
@ -20,34 +20,38 @@
|
|||
//! 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 struct-like definition describing the mapping between
|
||||
//! attribute names and the final value.
|
||||
//! It produces both an [`AttrParseState`] parser and a concrete struct to
|
||||
//! be yielded by the parser.
|
||||
//! The parser completes upon reaching a dead state.
|
||||
//! [`attr_parse_stream!`](crate::attr_parse_stream),
|
||||
//! which expects a `match`-like definition describing the mapping between
|
||||
//! attribute [QNames](crate::xir::QName) and a value derived from the
|
||||
//! attribute value.
|
||||
//! It produces a streaming attribute parser.
|
||||
//!
|
||||
//! Required attributes are checked after reaching a dead state,
|
||||
//! so attributes may appear in any order.
|
||||
//! 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,
|
||||
//! the struct produced by the parser will contain an [`Option`] type
|
||||
//! _only_ for the optional fields,
|
||||
//! freeing the caller from having to deal with wrapped values.
|
||||
//! duplicate attributes should be inhibited earlier in the process by XIR,
|
||||
//! if desired.
|
||||
//!
|
||||
//! The parser automatically produces detailed error and diagnostic
|
||||
//! messages.
|
||||
//! messages for unexpected attributes,
|
||||
//! or attributes that cannot be parsed into the final type.
|
||||
|
||||
use super::AttrParseError;
|
||||
use crate::{
|
||||
diagnose::Diagnostic,
|
||||
parse::{ClosedParseState, ParseState},
|
||||
parse::ClosedParseState,
|
||||
xir::{OpenSpan, QName},
|
||||
};
|
||||
use std::{convert::Infallible, fmt::Debug};
|
||||
|
||||
/// Attribute parsing automaton.
|
||||
///
|
||||
/// These parsers are generated by [`attr_parse!`](crate::attr_parse).
|
||||
/// These parsers are generated by
|
||||
/// [`attr_parse_stream!`](crate::attr_parse_stream).
|
||||
pub trait AttrParseState: ClosedParseState {
|
||||
/// Type of error for failed parsing of attribute values.
|
||||
///
|
||||
|
@ -72,26 +76,6 @@ pub trait AttrParseState: ClosedParseState {
|
|||
|
||||
/// Span associated with the element being parsed.
|
||||
fn element_span(&self) -> OpenSpan;
|
||||
|
||||
/// Attempt to narrow into the final type by checking the availability
|
||||
/// of required attribute values.
|
||||
///
|
||||
/// If a single missing required attribute does not have a value,
|
||||
/// this will fail with [`AttrParseError::MissingRequired`],
|
||||
/// which contains the parsing state that wraps every field type in
|
||||
/// [`Option`].
|
||||
/// The caller may use this to recover as much data as it can,
|
||||
/// or choose to allow it to fail with an error stating which fields
|
||||
/// are missing.
|
||||
/// The list of missing fields is generated dynamically during
|
||||
/// diagnostic reporting.
|
||||
fn finalize_attr(
|
||||
self,
|
||||
ctx: &mut <Self as ParseState>::Context,
|
||||
) -> Result<Self::Object, AttrParseError<Self>>;
|
||||
|
||||
/// Names of attributes that are required but do not yet have a value.
|
||||
fn required_missing(&self, ctx: &Self::Fields) -> Vec<QName>;
|
||||
}
|
||||
|
||||
/// Parse attributes for the given element.
|
||||
|
@ -104,64 +88,38 @@ pub fn parse_attrs<S: AttrParseState>(ele: QName, span: OpenSpan) -> S {
|
|||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! attr_parse {
|
||||
macro_rules! attr_parse_stream {
|
||||
($(#[$sattr:meta])*
|
||||
$(vis($vis:vis);)?
|
||||
$(type ValueError = $evty:ty;)?
|
||||
type Object = $objty:ty;
|
||||
type ValueError = $evty:ty;
|
||||
|
||||
struct $(#[$st_attr:meta])? $state_name:ident -> $struct_name:ident {
|
||||
$(#[$st_attr:meta])?
|
||||
$vis:vis $state_name:ident {
|
||||
$(
|
||||
$(#[$fattr:meta])*
|
||||
$field:ident: ($qname:ident $($fmod:tt)?) => $ty:ty,
|
||||
$qname:ident => $attrf:expr,
|
||||
)*
|
||||
}
|
||||
) => { 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.
|
||||
$crate::attr_parse!(@ty_assert $($fmod)? $ty);
|
||||
)*
|
||||
|
||||
$(#[$st_attr])?
|
||||
///
|
||||
#[doc=concat!("Parser producing [`", stringify!($struct_name), "`].")]
|
||||
///
|
||||
/// Unlike the final type,
|
||||
/// this is an intermediate representation that holds every
|
||||
/// pending value within an [`Option`] as it awaits further input,
|
||||
/// before finally being narrowed into
|
||||
#[doc=concat!("[`", stringify!($struct_name), "`].")]
|
||||
///
|
||||
/// This object is exposed for recovery and error reporting on
|
||||
/// [`AttrParseError::MissingRequired`][MissingRequired].
|
||||
///
|
||||
/// [MissingRequired]: crate::xir::parse::AttrParseError::MissingRequired
|
||||
// TODO: This can be extracted out of the macro.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
$($vis)? enum $state_name {
|
||||
$vis enum $state_name {
|
||||
Parsing(crate::xir::QName, crate::xir::OpenSpan),
|
||||
Done(crate::xir::QName, crate::xir::OpenSpan),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
$($vis)? type [<$state_name Context>] =
|
||||
crate::parse::Context<[<$state_name Fields>]>;
|
||||
|
||||
/// 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>] {
|
||||
$(
|
||||
// Value + key span
|
||||
pub $field: Option<($ty, crate::span::Span)>,
|
||||
)*
|
||||
}
|
||||
$vis struct [<$state_name Fields>];
|
||||
|
||||
impl crate::xir::parse::AttrParseState for $state_name {
|
||||
type ValueError = $crate::attr_parse!(@evty $($evty)?);
|
||||
type ValueError = $evty;
|
||||
type Fields = [<$state_name Fields>];
|
||||
|
||||
fn with_element(
|
||||
|
@ -182,79 +140,8 @@ macro_rules! attr_parse {
|
|||
Self::Parsing(_, span) | Self::Done(_, span) => *span,
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize_attr(
|
||||
self,
|
||||
ctx: &mut <Self as crate::parse::ParseState>::Context,
|
||||
) -> Result<
|
||||
Self::Object,
|
||||
crate::xir::parse::AttrParseError<Self>,
|
||||
> {
|
||||
// Will be unused if there are no fields.
|
||||
#[allow(unused_variables)]
|
||||
let fields: Self::Fields = std::mem::take(ctx);
|
||||
|
||||
// Validate required fields before we start moving data.
|
||||
$(
|
||||
$crate::attr_parse!(@if_missing_req $($fmod)? fields.$field {
|
||||
return Err(
|
||||
crate::xir::parse::AttrParseError::MissingRequired(
|
||||
self,
|
||||
fields,
|
||||
)
|
||||
)
|
||||
});
|
||||
)*
|
||||
|
||||
let obj = $struct_name {
|
||||
$(
|
||||
$field: $crate::attr_parse!(
|
||||
@maybe_value $($fmod)? fields.$field
|
||||
),
|
||||
)*
|
||||
};
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn required_missing(
|
||||
&self,
|
||||
#[allow(unused_variables)] // unused if no fields
|
||||
ctx: &Self::Fields
|
||||
) -> Vec<crate::xir::QName> {
|
||||
#[allow(unused_mut)]
|
||||
let mut missing = vec![];
|
||||
|
||||
$(
|
||||
$crate::attr_parse!(@if_missing_req $($fmod)? ctx.$field {
|
||||
missing.push($qname);
|
||||
});
|
||||
)*
|
||||
|
||||
missing
|
||||
}
|
||||
}
|
||||
|
||||
impl $state_name {
|
||||
fn done_with_element(
|
||||
ele: crate::xir::QName,
|
||||
span: crate::xir::OpenSpan,
|
||||
) -> Self {
|
||||
Self::Done(ele, span)
|
||||
}
|
||||
}
|
||||
|
||||
$(#[$sattr])*
|
||||
#[derive(Debug, PartialEq)]
|
||||
$($vis)? struct $struct_name {
|
||||
$(
|
||||
$(#[$fattr])*
|
||||
pub $field: $ty,
|
||||
)*
|
||||
}
|
||||
|
||||
impl crate::parse::Object for $struct_name {}
|
||||
|
||||
impl std::fmt::Display for $state_name {
|
||||
/// Additional error context shown in diagnostic messages for
|
||||
/// certain variants of [`ParseError`].
|
||||
|
@ -276,23 +163,25 @@ macro_rules! attr_parse {
|
|||
type Token = crate::xir::flat::XirfToken<
|
||||
crate::xir::flat::RefinedText
|
||||
>;
|
||||
type Object = $struct_name;
|
||||
type Object = $objty;
|
||||
type Error = crate::xir::parse::AttrParseError<Self>;
|
||||
type Context = [<$state_name Context>];
|
||||
|
||||
fn parse_token(
|
||||
#[allow(unused_mut)]
|
||||
mut self,
|
||||
tok: Self::Token,
|
||||
ctx: &mut Self::Context,
|
||||
_ctx: &mut Self::Context,
|
||||
) -> crate::parse::TransitionResult<Self> {
|
||||
use crate::parse::{Transition, Transitionable, ParseStatus};
|
||||
use crate::parse::Transition;
|
||||
use crate::xir::{
|
||||
flat,
|
||||
parse::{AttrParseError, AttrParseState}
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use crate::xir::attr::{Attr, AttrSpan}; // unused if no attrs
|
||||
#[allow(unused_imports)] // unused if no attrs
|
||||
use crate::{
|
||||
parse::{Transitionable, ParseStatus, util::SPair},
|
||||
xir::attr::{Attr, AttrSpan}
|
||||
};
|
||||
|
||||
let ele_name = self.element_name();
|
||||
|
||||
|
@ -303,47 +192,22 @@ macro_rules! attr_parse {
|
|||
// We don't use `$qname:pat` because we reuse
|
||||
// `$qname` for error messages.
|
||||
(st @ Self::Parsing(_, _), flat::XirfToken::Attr(
|
||||
attr @ Attr(qn, _, AttrSpan(kspan, _))
|
||||
Attr(qn, v, AttrSpan(_, vspan))
|
||||
)) if qn == $qname => {
|
||||
match ctx.$field {
|
||||
// Duplicate attribute name
|
||||
Some((_, first_kspan)) => {
|
||||
Transition(st).err(
|
||||
AttrParseError::DuplicateAttr(
|
||||
attr,
|
||||
first_kspan,
|
||||
ele_name,
|
||||
)
|
||||
match Into::<Result<$objty, $evty>>::into($attrf(SPair(v, vspan))) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
// First time seeing attribute name
|
||||
None => {
|
||||
let result = $crate::attr_parse!(
|
||||
@into_value $($fmod)? attr
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(value) => {
|
||||
ctx.$field.replace((
|
||||
value,
|
||||
kspan,
|
||||
));
|
||||
|
||||
Transition(st).incomplete()
|
||||
},
|
||||
|
||||
Err(e) => Transition(st).err(
|
||||
// Will complain about
|
||||
// `Into::into` if Infallible.
|
||||
#[allow(unreachable_code)]
|
||||
AttrParseError::InvalidValue(
|
||||
e.into(),
|
||||
ele_name,
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
@ -357,10 +221,7 @@ macro_rules! attr_parse {
|
|||
|
||||
// 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)
|
||||
Transition(Self::Done(ele, span)).dead(tok_dead)
|
||||
}
|
||||
|
||||
// Any tokens received after aggregation is completed
|
||||
|
@ -373,87 +234,12 @@ macro_rules! attr_parse {
|
|||
}
|
||||
|
||||
fn is_accepting(&self, _: &Self::Context) -> bool {
|
||||
// We must always be consumed via the dead state.
|
||||
false
|
||||
// All attributes are optional for this parser,
|
||||
// and each token is a complete attribute.
|
||||
true
|
||||
}
|
||||
}
|
||||
} };
|
||||
|
||||
// Optional attribute if input above is of the form `(QN_FOO?) => ...`.
|
||||
(@ty_assert ? $ty:ty) => {
|
||||
// This type assertion isn't supported by `assert_impl_all!`.
|
||||
// The error isn't the most clear,
|
||||
// but it's better than nothing and we can improve upon it later
|
||||
// on.
|
||||
const _: fn() = || {
|
||||
trait OptionFromAttr {}
|
||||
impl<T: TryFrom<Attr>> OptionFromAttr for Option<T> {}
|
||||
|
||||
// Fail when `$ty` is not Option<impl TryFrom<Attr>>.
|
||||
fn assert_attr_option<T: OptionFromAttr>() {}
|
||||
assert_attr_option::<$ty>();
|
||||
};
|
||||
};
|
||||
|
||||
(@ty_assert $ty:ty) => {
|
||||
assert_impl_all!($ty: TryFrom<crate::xir::attr::Attr>);
|
||||
};
|
||||
|
||||
(@evty $evty:ty) => {
|
||||
$evty
|
||||
};
|
||||
|
||||
// If no ValueError type is provided,
|
||||
// then it's not possible for values to fail parsing
|
||||
// (their SymbolId is their final value).
|
||||
(@evty) => {
|
||||
std::convert::Infallible
|
||||
};
|
||||
|
||||
// Optional attribute if input above is of the form `(QN_FOO?) => ...`.
|
||||
(@if_missing_req ? $from:ident.$field:ident $body:block) => {
|
||||
// This is an optional field;
|
||||
// do nothing.
|
||||
};
|
||||
|
||||
// Otherwise required.
|
||||
(@if_missing_req $from:ident.$field:ident $body:block) => {
|
||||
if $from.$field.is_none() {
|
||||
$body
|
||||
}
|
||||
};
|
||||
|
||||
// Optional attribute if input above is of the form `(QN_FOO?) => ...`.
|
||||
(@into_value ? $from:ident) => {
|
||||
$from.try_into().map(Some)
|
||||
};
|
||||
|
||||
(@into_value $from:ident) => {
|
||||
$from.try_into()
|
||||
};
|
||||
|
||||
// Optional attribute if input above is of the form `(QN_FOO?) => ...`.
|
||||
(@maybe_value ? $from:ident.$field:ident) => {
|
||||
// This does not produce a great error if the user forgets to use an
|
||||
// `Option` type for optional attributes,
|
||||
// but the comment is better than nothing.
|
||||
match $from.$field { // field type must be Option<T>
|
||||
Some((value, _kspan)) => value,
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
// Otherwise,
|
||||
// if the above `@maybe_value` does not match,
|
||||
// the attribute is required.
|
||||
(@maybe_value $from:ident.$field:ident) => {
|
||||
// This assumes that we've already validated via
|
||||
// `@validate_req` above,
|
||||
// and so should never actually panic.
|
||||
match $from.$field.unwrap() {
|
||||
(value, _kspan) => value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,632 +0,0 @@
|
|||
// XIR attribute parser generator tests
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
diagnose::AnnotatedSpan,
|
||||
parse::{ParseError, ParseState, Parsed, Parser, TokenStream},
|
||||
span::dummy::*,
|
||||
sym::SymbolId,
|
||||
xir::{
|
||||
attr::{Attr, AttrSpan},
|
||||
flat::{test::close_empty, Depth, XirfToken},
|
||||
st::qname::*,
|
||||
},
|
||||
};
|
||||
use std::{assert_matches::assert_matches, error::Error, fmt::Display};
|
||||
|
||||
const SE: OpenSpan = OpenSpan(S1.offset_add(100).unwrap(), 0);
|
||||
|
||||
// Random choice of QName for tests.
|
||||
const QN_ELE: QName = QN_YIELDS;
|
||||
|
||||
fn parse_aggregate<S: AttrParseState>(
|
||||
toks: impl TokenStream<S::Token>,
|
||||
) -> Result<(S::Object, S::Token), ParseError<S::Token, S::Error>>
|
||||
where
|
||||
S: AttrParseState,
|
||||
S::Context: Default,
|
||||
{
|
||||
parse_aggregate_with(&mut Parser::with_state(
|
||||
S::with_element(QN_ELE, SE),
|
||||
toks,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_aggregate_with<S: AttrParseState, I>(
|
||||
sut: &mut Parser<S, I>,
|
||||
) -> Result<(S::Object, S::Token), ParseError<S::Token, S::Error>>
|
||||
where
|
||||
S: ParseState,
|
||||
S::Context: Default,
|
||||
I: TokenStream<S::Token>,
|
||||
{
|
||||
let mut obj = None;
|
||||
|
||||
for item in sut {
|
||||
match item {
|
||||
Ok(Parsed::Object(result)) => {
|
||||
obj.replace(result);
|
||||
}
|
||||
Ok(Parsed::Incomplete) => continue,
|
||||
// This represents the dead state,
|
||||
// since this is the top-level parser.
|
||||
Err(ParseError::UnexpectedToken(tok, _)) => {
|
||||
return Ok((
|
||||
obj.expect(
|
||||
"parser did not produce aggregate attribute object",
|
||||
),
|
||||
tok,
|
||||
))
|
||||
}
|
||||
Err(other) => return Err(other),
|
||||
}
|
||||
}
|
||||
|
||||
panic!("expected AttrParseState dead state (obj: {obj:?})");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_with_values() {
|
||||
attr_parse! {
|
||||
struct ReqValuesState -> ReqValues {
|
||||
name: (QN_NAME) => Attr,
|
||||
yields: (QN_YIELDS) => Attr,
|
||||
}
|
||||
}
|
||||
|
||||
let attr_name = Attr(QN_NAME, "val_name".into(), AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, "val_value".into(), AttrSpan(S2, S3));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok((
|
||||
ReqValues {
|
||||
name: attr_name,
|
||||
yields: attr_yields,
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate::<ReqValuesState>(toks),
|
||||
);
|
||||
}
|
||||
|
||||
// Same as above test,
|
||||
// but the order of the tokens is swapped.
|
||||
#[test]
|
||||
fn required_with_values_out_of_order() {
|
||||
attr_parse! {
|
||||
struct ReqValuesState -> ReqValues {
|
||||
name: (QN_NAME) => Attr,
|
||||
yields: (QN_YIELDS) => Attr,
|
||||
}
|
||||
}
|
||||
|
||||
let attr_name = Attr(QN_NAME, "val_name".into(), AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, "val_value".into(), AttrSpan(S2, S3));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
// @yields then @name just to emphasize that order does not matter.
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok((
|
||||
ReqValues {
|
||||
name: attr_name,
|
||||
yields: attr_yields,
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate::<ReqValuesState>(toks),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_with_values() {
|
||||
attr_parse! {
|
||||
struct OptValuesState -> OptValues {
|
||||
name: (QN_NAME?) => Option<Attr>,
|
||||
yields: (QN_YIELDS?) => Option<Attr>,
|
||||
}
|
||||
}
|
||||
|
||||
let attr_name = Attr(QN_NAME, "val_name".into(), AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, "val_value".into(), AttrSpan(S2, S3));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok((
|
||||
OptValues {
|
||||
name: Some(attr_name),
|
||||
yields: Some(attr_yields),
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate::<OptValuesState>(toks),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_value_into() {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Foo(SymbolId);
|
||||
|
||||
impl From<Attr> for Foo {
|
||||
fn from(attr: Attr) -> Self {
|
||||
Foo(attr.value())
|
||||
}
|
||||
}
|
||||
|
||||
attr_parse! {
|
||||
// Note that associated type `ValueError` defaults to `Infallible`,
|
||||
// which is why `From` is sufficient above.
|
||||
struct ValueIntoState -> ValueInto {
|
||||
name: (QN_NAME) => Foo,
|
||||
yields: (QN_YIELDS?) => Option<Foo>,
|
||||
}
|
||||
}
|
||||
|
||||
let val_name = "val_name".into();
|
||||
let val_yields = "val_yields".into();
|
||||
let attr_name = Attr(QN_NAME, val_name, AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, val_yields, AttrSpan(S2, S3));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok((
|
||||
ValueInto {
|
||||
name: Foo(val_name),
|
||||
yields: Some(Foo(val_yields)),
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate::<ValueIntoState>(toks),
|
||||
);
|
||||
}
|
||||
|
||||
// This test would fail at compile time.
|
||||
#[test]
|
||||
fn attr_value_error() {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Foo;
|
||||
|
||||
impl TryFrom<Attr> for Foo {
|
||||
type Error = FooError;
|
||||
|
||||
fn try_from(attr: Attr) -> Result<Self, Self::Error> {
|
||||
Err(FooError(attr.value()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct FooError(SymbolId);
|
||||
|
||||
impl Error for FooError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FooError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "test FooError")
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for FooError {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
attr_parse! {
|
||||
type ValueError = FooError;
|
||||
|
||||
struct ValueIntoState -> ValueInto {
|
||||
name: (QN_NAME) => Foo,
|
||||
yields: (QN_YIELDS?) => Option<Foo>,
|
||||
}
|
||||
}
|
||||
|
||||
let val_name = "val_name".into();
|
||||
let val_yields = "val_yields".into();
|
||||
let attr_name = Attr(QN_NAME, val_name, AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, val_yields, AttrSpan(S2, S3));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
];
|
||||
|
||||
let mut sut = Parser::with_state(
|
||||
ValueIntoState::with_element(QN_ELE, SE),
|
||||
toks.into_iter(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sut.next(),
|
||||
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
|
||||
FooError(val_name),
|
||||
QN_ELE
|
||||
))))
|
||||
);
|
||||
|
||||
// TryInto on `Option` inner type.
|
||||
assert_eq!(
|
||||
sut.next(),
|
||||
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
|
||||
FooError(val_yields),
|
||||
QN_ELE
|
||||
))))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_with_all_missing() {
|
||||
attr_parse! {
|
||||
struct OptMissingState -> OptMissing {
|
||||
name: (QN_NAME?) => Option<Attr>,
|
||||
yields: (QN_YIELDS?) => Option<Attr>,
|
||||
}
|
||||
}
|
||||
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok((
|
||||
OptMissing {
|
||||
name: None,
|
||||
yields: None,
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate::<OptMissingState>(toks),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_some_optional_missing() {
|
||||
attr_parse! {
|
||||
struct MixedState -> Mixed {
|
||||
name: (QN_NAME) => Attr,
|
||||
src: (QN_SRC?) => Option<Attr>,
|
||||
yields: (QN_YIELDS?) => Option<Attr>,
|
||||
}
|
||||
}
|
||||
|
||||
let attr_name = Attr(QN_NAME, "val_name".into(), AttrSpan(S1, S2));
|
||||
let attr_src = Attr(QN_SRC, "val_src".into(), AttrSpan(S2, S3));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
// `name` and `src` but no optional `yields`.
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_src.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok((
|
||||
Mixed {
|
||||
name: attr_name,
|
||||
src: Some(attr_src),
|
||||
yields: None,
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate::<MixedState>(toks),
|
||||
);
|
||||
}
|
||||
|
||||
mod required {
|
||||
use super::*;
|
||||
use crate::{sym::st, xir::EleSpan};
|
||||
|
||||
attr_parse! {
|
||||
struct ReqMissingState -> ReqMissing {
|
||||
name: (QN_NAME) => Attr,
|
||||
src: (QN_SRC) => Attr,
|
||||
ty: (QN_TYPE) => Attr,
|
||||
yields: (QN_YIELDS) => Attr,
|
||||
}
|
||||
}
|
||||
|
||||
const ATTR_NAME: Attr = Attr(QN_NAME, st::raw::L_NAME, AttrSpan(S1, S2));
|
||||
const ATTR_YIELDS: Attr =
|
||||
Attr(QN_YIELDS, st::raw::L_VALUE, AttrSpan(S2, S3));
|
||||
|
||||
#[test]
|
||||
fn required_missing_values() {
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(ATTR_NAME),
|
||||
// <Missing @src, but no error yet.>
|
||||
// <Missing @type, but no error yet.>
|
||||
XirfToken::Attr(ATTR_YIELDS),
|
||||
// Will cause dead state,
|
||||
// which will then trigger the error:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
let err = parse_aggregate::<ReqMissingState>(toks)
|
||||
.expect_err("expected failure from missing attributes");
|
||||
|
||||
let sut = ReqMissingState::with_element(QN_ELE, SE);
|
||||
|
||||
// The error should provide the state of the parser during the
|
||||
// finalization step.
|
||||
// Since this happens in a dead state,
|
||||
// we must also receive the token that triggered it,
|
||||
// just as we would normally receive on successful parsing.
|
||||
assert_matches!(
|
||||
err,
|
||||
ParseError::StateError(AttrParseError::MissingRequired(
|
||||
given_sut,
|
||||
ReqMissingStateFields {
|
||||
name: Some((ref given_name, _)),
|
||||
src: None, // cause of the error
|
||||
ty: None, // another cause of the error
|
||||
yields: Some((ref given_yields, _)),
|
||||
..
|
||||
},
|
||||
)) if given_sut == sut
|
||||
&& given_name == &ATTR_NAME
|
||||
&& given_yields == &ATTR_YIELDS
|
||||
);
|
||||
}
|
||||
|
||||
/// Relies on [`required_missing_values`] above to verify state of the
|
||||
/// parser used in the error.
|
||||
#[test]
|
||||
fn error_contains_all_required_missing_attr_names() {
|
||||
// Manually construct the partial state rather than parsing tokens.
|
||||
// `required_missing_values` above verifies that this state is what
|
||||
// is in fact constructed from a failed parsing attempt.
|
||||
let sut = ReqMissingState::with_element(QN_ELE, SE);
|
||||
let mut partial = ReqMissingStateFields::default();
|
||||
partial.name.replace((ATTR_NAME, S1));
|
||||
partial.yields.replace((ATTR_YIELDS, S2));
|
||||
|
||||
let err = AttrParseError::MissingRequired(sut, partial);
|
||||
|
||||
// When represented as a string,
|
||||
// the error should produce _all_ required attributes that do not
|
||||
// have values,
|
||||
// rather than requiring the user to fix one and re-compile only
|
||||
// to encounter another,
|
||||
// and potentially repeat multiple times.
|
||||
let err_str = err.to_string();
|
||||
assert!(
|
||||
err_str.contains(&format!("@{QN_SRC}")),
|
||||
"\"{err_str}\" must contain \"@{QN_SRC}\""
|
||||
);
|
||||
assert!(
|
||||
err_str.contains(&format!("@{QN_TYPE}")),
|
||||
"\"{err_str}\" must contain \"@{QN_TYPE}\""
|
||||
);
|
||||
|
||||
// The error should also reference the element name
|
||||
// (which is provided in `parse_aggregate`).
|
||||
assert!(
|
||||
err_str.contains(&QN_ELE.to_string()),
|
||||
"\"{err_str}\" must contain name of element being parsed"
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [`error_contains_all_required_missing_attr_names`].
|
||||
#[test]
|
||||
fn diagnostic_message_contains_all_required_missing_attr_name() {
|
||||
let sut = ReqMissingState::with_element(QN_ELE, SE);
|
||||
let mut partial = ReqMissingStateFields::default();
|
||||
partial.name.replace((ATTR_NAME, S1));
|
||||
partial.yields.replace((ATTR_YIELDS, S2));
|
||||
|
||||
let err = AttrParseError::MissingRequired(sut, partial);
|
||||
let desc = err.describe();
|
||||
|
||||
// The diagnostic message should reference the element.
|
||||
assert_eq!(desc[0].span(), SE.span());
|
||||
|
||||
// It should re-state the required attributes,
|
||||
// since this is where the user will most likely be looking.
|
||||
let label_str = desc[0]
|
||||
.label()
|
||||
.expect("missing diagnostic label")
|
||||
.to_string();
|
||||
|
||||
assert!(
|
||||
label_str.contains(&format!("@{QN_SRC}")),
|
||||
"diagnostic label \"{label_str}\" must contain \"@{QN_SRC}\""
|
||||
);
|
||||
assert!(
|
||||
label_str.contains(&format!("@{QN_TYPE}")),
|
||||
"diagnostic label \"{label_str}\" must contain \"@{QN_TYPE}\""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_attr_with_recovery() {
|
||||
attr_parse! {
|
||||
struct UnexpectedState -> Unexpected {
|
||||
name: (QN_NAME) => Attr,
|
||||
src: (QN_SRC) => Attr,
|
||||
}
|
||||
}
|
||||
|
||||
let attr_name = Attr(QN_NAME, "val_name".into(), AttrSpan(S1, S2));
|
||||
let attr_unexpected = Attr(QN_TYPE, "unexpected".into(), AttrSpan(S1, S2));
|
||||
let attr_src = Attr(QN_SRC, "val_src".into(), AttrSpan(S2, S3));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
// This is expected:
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
// NOT expected (produce an error):
|
||||
XirfToken::Attr(attr_unexpected.clone()),
|
||||
// <Recovery must take place here.>
|
||||
// This is expected after recovery:
|
||||
XirfToken::Attr(attr_src.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
let mut sut =
|
||||
Parser::with_state(UnexpectedState::with_element(QN_ELE, SE), toks);
|
||||
|
||||
// This will fail at the unknown attribute,
|
||||
// and must then remain in a state where parsing can be resumed.
|
||||
// This simply means ignoring the provided attribute,
|
||||
// which in XIRF is discarding a single token of input,
|
||||
// rather than having to continue parsing the attribute to then
|
||||
// discard.
|
||||
assert_eq!(
|
||||
Err(ParseError::StateError(AttrParseError::UnexpectedAttr(
|
||||
attr_unexpected,
|
||||
QN_ELE,
|
||||
))),
|
||||
parse_aggregate_with(&mut sut),
|
||||
);
|
||||
|
||||
// The final result,
|
||||
// after having failed and recovered.
|
||||
assert_eq!(
|
||||
Ok((
|
||||
Unexpected {
|
||||
name: attr_name,
|
||||
src: attr_src,
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate_with(&mut sut),
|
||||
);
|
||||
}
|
||||
|
||||
// A duplicate attribute will result in an error,
|
||||
// and recovery will cause the duplicate to be ignored.
|
||||
#[test]
|
||||
fn duplicate_attr_with_recovery() {
|
||||
attr_parse! {
|
||||
struct DupState -> Dup {
|
||||
name: (QN_NAME) => Attr,
|
||||
src: (QN_SRC) => Attr,
|
||||
}
|
||||
}
|
||||
|
||||
let attr_keep = Attr(QN_NAME, "keep me".into(), AttrSpan(S1, S2));
|
||||
let attr_dup = Attr(QN_NAME, "duplicate".into(), AttrSpan(S2, S3));
|
||||
let attr_src = Attr(QN_SRC, "val_src".into(), AttrSpan(S3, S1));
|
||||
let tok_dead = close_empty(S3, Depth(0));
|
||||
|
||||
let toks = vec![
|
||||
// Both of these have the same name (@name).
|
||||
XirfToken::Attr(attr_keep.clone()),
|
||||
XirfToken::Attr(attr_dup.clone()),
|
||||
// Another attribute just to show that error recovery permits
|
||||
// further attribute parsing.
|
||||
XirfToken::Attr(attr_src.clone()),
|
||||
// Will cause dead state:
|
||||
tok_dead.clone(),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
let mut sut = Parser::with_state(DupState::with_element(QN_ELE, SE), toks);
|
||||
|
||||
// The first token is good,
|
||||
// since we haven't encountered the attribute yet.
|
||||
assert_eq!(sut.next(), Some(Ok(Parsed::Incomplete)));
|
||||
|
||||
// The second one results in an error,
|
||||
// since the name is the same.
|
||||
let err = sut
|
||||
.next()
|
||||
.unwrap()
|
||||
.expect_err("DuplicateAttr error expected");
|
||||
|
||||
assert_eq!(
|
||||
ParseError::StateError(AttrParseError::DuplicateAttr(
|
||||
attr_dup,
|
||||
attr_keep.attr_span().key_span(),
|
||||
QN_ELE,
|
||||
)),
|
||||
err,
|
||||
);
|
||||
|
||||
// The diagnostic description of this error should contain first a
|
||||
// reference to the original attribute,
|
||||
// and then a reference to the duplicate.
|
||||
let desc = err.describe();
|
||||
assert_eq!(desc[0].span(), S1);
|
||||
assert_eq!(desc[1].span(), S2);
|
||||
|
||||
// Once parsing is completed,
|
||||
// we must have kept the first occurrence of the attribute and
|
||||
// discarded the second.
|
||||
assert_eq!(
|
||||
Ok((
|
||||
Dup {
|
||||
name: attr_keep,
|
||||
src: attr_src,
|
||||
},
|
||||
tok_dead
|
||||
)),
|
||||
parse_aggregate_with(&mut sut),
|
||||
);
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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 a value derived from the
|
||||
//! attribute 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;
|
||||
|
||||
$(#[$st_attr:meta])?
|
||||
$vis:vis $state_name:ident {
|
||||
$(
|
||||
$(#[$fattr:meta])*
|
||||
$qname:ident => $attrf:expr,
|
||||
)*
|
||||
}
|
||||
) => { paste::paste! {
|
||||
$(#[$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 <Self as crate::parse::ParseState>::Context,
|
||||
) -> Result<
|
||||
Self::Object,
|
||||
crate::xir::parse::AttrParseError<Self>,
|
||||
> {
|
||||
unimplemented!("attrstream finalize_attr")
|
||||
}
|
||||
|
||||
fn required_missing(
|
||||
&self,
|
||||
#[allow(unused_variables)] // unused if no fields
|
||||
_ctx: &Self::Fields
|
||||
) -> Vec<crate::xir::QName> {
|
||||
unimplemented!("attrstream required_missing")
|
||||
}
|
||||
}
|
||||
|
||||
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<Self>;
|
||||
|
||||
fn parse_token(
|
||||
#[allow(unused_mut)]
|
||||
mut self,
|
||||
tok: Self::Token,
|
||||
_ctx: &mut Self::Context,
|
||||
) -> crate::parse::TransitionResult<Self> {
|
||||
use crate::parse::Transition;
|
||||
use crate::xir::{
|
||||
flat,
|
||||
parse::{AttrParseError, AttrParseState}
|
||||
};
|
||||
#[allow(unused_imports)] // unused if no attrs
|
||||
use crate::{
|
||||
parse::{Transitionable, ParseStatus, util::SPair},
|
||||
xir::attr::{Attr, AttrSpan}
|
||||
};
|
||||
|
||||
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(qn, v, AttrSpan(_, vspan))
|
||||
)) if qn == $qname => {
|
||||
match Into::<Result<$objty, $evty>>::into($attrf(SPair(v, vspan))) {
|
||||
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) => {
|
||||
Transition(Self::Done(ele, span)).dead(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;
|
|
@ -1,264 +0,0 @@
|
|||
// XIR attribute parser generator tests
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::super::{AttrParseError, AttrParseState};
|
||||
use crate::{
|
||||
diagnose::{AnnotatedSpan, Diagnostic},
|
||||
parse::{self, util::SPair, ParseError, Parsed, Parser, TokenStream},
|
||||
span::dummy::*,
|
||||
xir::{
|
||||
attr::{Attr, AttrSpan},
|
||||
flat::XirfToken,
|
||||
st::qname::*,
|
||||
OpenSpan, QName,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error,
|
||||
fmt::{Debug, Display},
|
||||
iter,
|
||||
};
|
||||
|
||||
use Parsed::Object;
|
||||
|
||||
const SE: OpenSpan = OpenSpan(S1.offset_add(100).unwrap(), 0);
|
||||
|
||||
// Random choice of QName for tests.
|
||||
const QN_ELE: QName = QN_YIELDS;
|
||||
|
||||
fn sut_parse<S: AttrParseState, I: TokenStream<S::Token>>(
|
||||
toks: I,
|
||||
) -> Parser<S, I>
|
||||
where
|
||||
S: AttrParseState,
|
||||
S::Context: Default,
|
||||
{
|
||||
Parser::with_state(S::with_element(QN_ELE, SE), toks)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Foo {
|
||||
A(SPair),
|
||||
B(SPair),
|
||||
Unused(SPair),
|
||||
}
|
||||
|
||||
impl parse::Object for Foo {}
|
||||
|
||||
impl<E> From<Foo> for Result<Foo, E> {
|
||||
fn from(value: Foo) -> Self {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Remember: we only describe what is _permissable_,
|
||||
// not what is required or what order it must appear in.
|
||||
// That is the responsibility of parsers lower in the pipeline.
|
||||
#[test]
|
||||
fn attrs_any_order_and_optional() {
|
||||
attr_parse_stream! {
|
||||
type Object = Foo;
|
||||
type ValueError = Infallible;
|
||||
|
||||
ValuesState {
|
||||
QN_NAME => Foo::A,
|
||||
|
||||
// The above is the same as this longer form:
|
||||
QN_YIELDS => |spair| Foo::B(spair),
|
||||
|
||||
// No value will be provided for this one,
|
||||
// which is okay since all are implicitly optional.
|
||||
QN_INDEX => Foo::Unused,
|
||||
}
|
||||
}
|
||||
|
||||
let name = "val_name".into();
|
||||
let yields = "val_value".into();
|
||||
let attr_name = Attr(QN_NAME, name, AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, yields, AttrSpan(S2, S3));
|
||||
|
||||
// @yields then @name just to emphasize that order does not matter.
|
||||
let toks = vec![XirfToken::Attr(attr_yields), XirfToken::Attr(attr_name)];
|
||||
|
||||
assert_eq!(
|
||||
// Simply parses back out the attributes;
|
||||
// see following tests further value parsing.
|
||||
// Note that we omit one of the attributes declared above.
|
||||
Ok(vec![
|
||||
Object(Foo::B(SPair(yields, S3))),
|
||||
Object(Foo::A(SPair(name, S2)))
|
||||
]),
|
||||
sut_parse::<ValuesState, _>(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
// Since all are optional,
|
||||
// the attribute list can be empty.
|
||||
#[test]
|
||||
fn attrs_empty() {
|
||||
attr_parse_stream! {
|
||||
type Object = Foo;
|
||||
type ValueError = Infallible;
|
||||
|
||||
ValuesState {
|
||||
// We will not provide a value for this.
|
||||
QN_NAME => Foo::Unused,
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
// Simply parses back out the attributes;
|
||||
// see following tests further value parsing.
|
||||
Ok(vec![]),
|
||||
sut_parse::<ValuesState, _>(iter::empty()).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attr_value_error() {
|
||||
impl TryFrom<SPair> for Foo {
|
||||
type Error = FooError;
|
||||
|
||||
fn try_from(spair: SPair) -> Result<Self, Self::Error> {
|
||||
Err(FooError(spair))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct FooError(SPair);
|
||||
|
||||
impl Error for FooError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FooError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "test FooError")
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for FooError {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Result<Foo, FooError>> for FooError {
|
||||
fn into(self) -> Result<Foo, FooError> {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
|
||||
attr_parse_stream! {
|
||||
type Object = Foo;
|
||||
type ValueError = FooError;
|
||||
|
||||
ValueTryIntoState {
|
||||
// Explicit form:
|
||||
QN_NAME => |s| Err(FooError(s)),
|
||||
|
||||
// Conversion using `Into`:
|
||||
QN_YIELDS => FooError,
|
||||
}
|
||||
}
|
||||
|
||||
let val_name = "val_name".into();
|
||||
let val_yields = "val_yields".into();
|
||||
let attr_name = Attr(QN_NAME, val_name, AttrSpan(S1, S2));
|
||||
let attr_yields = Attr(QN_YIELDS, val_yields, AttrSpan(S2, S3));
|
||||
|
||||
let toks = vec![
|
||||
XirfToken::Attr(attr_name.clone()),
|
||||
XirfToken::Attr(attr_yields.clone()),
|
||||
];
|
||||
|
||||
let mut sut = sut_parse::<ValueTryIntoState, _>(toks.into_iter());
|
||||
|
||||
assert_eq!(
|
||||
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
|
||||
FooError(SPair(val_name, S2)),
|
||||
QN_ELE
|
||||
)))),
|
||||
sut.next(),
|
||||
);
|
||||
|
||||
// TryInto on `Option` inner type.
|
||||
assert_eq!(
|
||||
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
|
||||
FooError(SPair(val_yields, S3)),
|
||||
QN_ELE
|
||||
)))),
|
||||
sut.next(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_attr_with_recovery() {
|
||||
attr_parse_stream! {
|
||||
type Object = Foo;
|
||||
type ValueError = Infallible;
|
||||
|
||||
UnexpectedState {
|
||||
QN_NAME => Foo::A,
|
||||
QN_SRC => Foo::B,
|
||||
}
|
||||
}
|
||||
|
||||
let name = "val_name".into();
|
||||
let src = "val_src".into();
|
||||
let attr_name = Attr(QN_NAME, name, AttrSpan(S1, S2));
|
||||
let attr_unexpected = Attr(QN_TYPE, "unexpected".into(), AttrSpan(S1, S2));
|
||||
let attr_src = Attr(QN_SRC, src, AttrSpan(S2, S3));
|
||||
|
||||
let toks = vec![
|
||||
// This is expected:
|
||||
XirfToken::Attr(attr_name),
|
||||
// NOT expected (produce an error):
|
||||
XirfToken::Attr(attr_unexpected.clone()),
|
||||
// <Recovery must take place here.>
|
||||
// This is expected after recovery:
|
||||
XirfToken::Attr(attr_src),
|
||||
];
|
||||
|
||||
let mut sut = Parser::with_state(
|
||||
UnexpectedState::with_element(QN_ELE, SE),
|
||||
toks.into_iter(),
|
||||
);
|
||||
|
||||
assert_eq!(sut.next(), Some(Ok(Object(Foo::A(SPair(name, S2))))));
|
||||
|
||||
// This will fail at the unknown attribute,
|
||||
// and must then remain in a state where parsing can be resumed.
|
||||
// This simply means ignoring the provided attribute,
|
||||
// which in XIRF is discarding a single token of input,
|
||||
// rather than having to continue parsing the attribute to then
|
||||
// discard.
|
||||
assert_eq!(
|
||||
sut.next(),
|
||||
Some(Err(ParseError::StateError(AttrParseError::UnexpectedAttr(
|
||||
attr_unexpected,
|
||||
QN_ELE,
|
||||
)))),
|
||||
);
|
||||
|
||||
assert_eq!(sut.next(), Some(Ok(Object(Foo::B(SPair(src, S3))))));
|
||||
}
|
|
@ -28,12 +28,11 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
|
||||
fmt::{DisplayFn, DisplayWrapper, ListDisplayWrapper, TtQuote},
|
||||
fmt::{DisplayFn, DisplayWrapper, TtQuote},
|
||||
span::Span,
|
||||
xir::{
|
||||
attr::Attr,
|
||||
flat::{RefinedText, XirfToken},
|
||||
fmt::XmlAttrList,
|
||||
EleSpan, OpenSpan, QName,
|
||||
},
|
||||
};
|
||||
|
@ -167,48 +166,15 @@ impl<NT: SumNt> Diagnostic for SumNtError<NT> {
|
|||
}
|
||||
|
||||
pub type ElementQName = QName;
|
||||
pub type FirstSpan = Span;
|
||||
|
||||
/// Error while parsing element attributes.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AttrParseError<S: AttrParseState> {
|
||||
/// One or more required attributes are missing.
|
||||
///
|
||||
/// Since required attributes are not checked until parsing is complete,
|
||||
/// and that determination requires a token of lookahead,
|
||||
/// this error produces a lookahead token that must be handled by the
|
||||
/// caller.
|
||||
///
|
||||
/// This also provices the actual [`AttrParseState`],
|
||||
/// which can be used to retrieve the missing required attributes
|
||||
/// (using [`AttrParseState::required_missing`]),
|
||||
/// can be used to retrieve information about the attributes that
|
||||
/// _have_ been successfully parsed,
|
||||
/// and can be used to resume parsing if desired.
|
||||
///
|
||||
/// The caller must determine whether to proceed with parsing of the
|
||||
/// element despite these problems;
|
||||
/// such recovery is beyond the scope of this parser.
|
||||
MissingRequired(S, S::Fields),
|
||||
|
||||
/// An attribute was encountered that was not expected by this parser.
|
||||
///
|
||||
/// Parsing may recover by simply ignoring this attribute.
|
||||
UnexpectedAttr(Attr, ElementQName),
|
||||
|
||||
/// An attribute with the same name as a previous attribute has been
|
||||
/// encountered within the context of this element.
|
||||
///
|
||||
/// The duplicate attribute is provided in its entirety.
|
||||
/// The key span of the first-encountered attribute of the same name is
|
||||
/// included to provide more robust diagnostic information.
|
||||
/// The value of the previous attribute is not included because it is
|
||||
/// expected that the diagnostic system will render the code
|
||||
/// associated with the span;
|
||||
/// displaying an attribute value in an error message is asking for
|
||||
/// too much trouble given that it is arbitrary text.
|
||||
DuplicateAttr(Attr, FirstSpan, ElementQName),
|
||||
|
||||
/// An error occurred while parsing an attribute value into the
|
||||
/// declared type.
|
||||
InvalidValue(S::ValueError, ElementQName),
|
||||
|
@ -217,13 +183,6 @@ pub enum AttrParseError<S: AttrParseState> {
|
|||
impl<S: AttrParseState> Display for AttrParseError<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MissingRequired(st, fields) => {
|
||||
let ele_name = st.element_name();
|
||||
write!(f, "element `{ele_name}` missing required ")?;
|
||||
|
||||
XmlAttrList::fmt(&st.required_missing(fields), f)
|
||||
}
|
||||
|
||||
Self::UnexpectedAttr(attr, ele_name) => {
|
||||
write!(
|
||||
f,
|
||||
|
@ -232,14 +191,6 @@ impl<S: AttrParseState> Display for AttrParseError<S> {
|
|||
)
|
||||
}
|
||||
|
||||
Self::DuplicateAttr(attr, _, ele_name) => {
|
||||
write!(
|
||||
f,
|
||||
"duplicate attribute `{attr}` for \
|
||||
element element `{ele_name}`"
|
||||
)
|
||||
}
|
||||
|
||||
Self::InvalidValue(ev, ele_name) => {
|
||||
Display::fmt(ev, f)?;
|
||||
write!(f, " for element {}", TtQuote::wrap(ele_name))
|
||||
|
@ -257,34 +208,12 @@ impl<S: AttrParseState> Error for AttrParseError<S> {
|
|||
impl<S: AttrParseState> Diagnostic for AttrParseError<S> {
|
||||
fn describe(&self) -> Vec<AnnotatedSpan> {
|
||||
match self {
|
||||
Self::MissingRequired(st, fields) => st
|
||||
.element_span()
|
||||
.tag_span()
|
||||
.error(format!(
|
||||
"missing required {}",
|
||||
XmlAttrList::wrap(&st.required_missing(fields)),
|
||||
))
|
||||
.into(),
|
||||
|
||||
// TODO: help stating attributes that can appear instead
|
||||
Self::UnexpectedAttr(attr @ Attr(.., aspan), ele_name) => aspan
|
||||
.key_span()
|
||||
.error(format!("element `{ele_name}` cannot contain `{attr}`"))
|
||||
.into(),
|
||||
|
||||
Self::DuplicateAttr(Attr(name, _, aspan), first_span, _) => {
|
||||
vec![
|
||||
first_span.note(format!(
|
||||
"{} previously encountered here",
|
||||
TtQuote::wrap(name)
|
||||
)),
|
||||
aspan.key_span().error(format!(
|
||||
"{} here is a duplicate",
|
||||
TtQuote::wrap(name)
|
||||
)),
|
||||
]
|
||||
}
|
||||
|
||||
Self::InvalidValue(ev, _) => ev.describe(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue