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-13346
main
Mike Gerwitz 2022-12-01 10:35:45 -05:00
parent ab0e4151a1
commit f872181f64
6 changed files with 57 additions and 1457 deletions

View File

@ -23,7 +23,6 @@
//! processing of the input XML to ensure well-formedness.
mod attr;
mod attrstream;
mod ele;
mod error;

View File

@ -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)]

View File

@ -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),
);
}

View File

@ -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;

View File

@ -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))))));
}

View File

@ -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(),
}
}