tamer: xir::parse: Extract and generalize NT errors

This is the same as the previous commits, but for non-sum NTs.

This also extracts errors into a separate module, which I had hoped to do in
a separate commit, but it's not worth separating them.  My _original_ reason
for doing so was debugging (I'll get into that below), but I had wanted to
trim down `ele.rs` anyway, since that mess is large and a lot to grok.

My debugging was trying to figure out why Rust was failing to derive
`PartialEq` on `NtError` because of `AttrParseError`.  As it turns out,
`AttrParseError::InvalidValue` was failing, thus the introduction of the
`PartialEq` trait bound on `AttrParseState::ValueError`.  Figuring this out
required implementing `PartialEq` myself without `derive` (well, using LSP,
which did all the work for me).

I'm not sure why this was not failing previously, which is a bit of a
concern, though perhaps in the context of the macro-expanded code, Rust was
able to properly resolve the types.

DEV-7145
main
Mike Gerwitz 2022-09-13 15:39:10 -04:00
parent 5078bd8bda
commit 212ca06efe
5 changed files with 336 additions and 314 deletions

View File

@ -24,9 +24,10 @@
mod attr;
mod ele;
mod error;
pub use attr::{parse_attrs, AttrParseError, AttrParseState};
pub use attr::{parse_attrs, AttrParseState};
pub use ele::{
EleParseState, NodeMatcher, StateStack, StateStackContext, SumNt,
SumNtError,
EleParseState, NodeMatcher, Nt, StateStack, StateStackContext, SumNt,
};
pub use error::{AttrParseError, NtError, SumNtError};

View File

@ -37,142 +37,13 @@
//! The parser automatically produces detailed error and diagnostic
//! messages.
use super::AttrParseError;
use crate::{
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
fmt::ListDisplayWrapper,
diagnose::Diagnostic,
parse::ClosedParseState,
span::Span,
xir::{attr::Attr, fmt::XmlAttrList, EleSpan, OpenSpan, QName},
xir::{OpenSpan, QName},
};
use std::{convert::Infallible, error::Error, fmt::Display};
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),
/// 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),
}
impl<S: AttrParseState> Display for AttrParseError<S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::fmt::{DisplayWrapper, TtQuote};
match self {
Self::MissingRequired(st) => {
let ele_name = st.element_name();
write!(f, "element `{ele_name}` missing required ")?;
XmlAttrList::fmt(&st.required_missing(), f)
}
Self::UnexpectedAttr(attr, ele_name) => {
write!(
f,
"unexpected attribute `{attr}` for \
element `{ele_name}`"
)
}
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))
}
}
}
}
impl<S: AttrParseState> Error for AttrParseError<S> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
impl<S: AttrParseState> Diagnostic for AttrParseError<S> {
fn describe(&self) -> Vec<AnnotatedSpan> {
use crate::fmt::{DisplayWrapper, TtQuote};
match self {
Self::MissingRequired(st) => st
.element_span()
.tag_span()
.error(format!(
"missing required {}",
XmlAttrList::wrap(&st.required_missing()),
))
.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(),
}
}
}
use std::convert::Infallible;
/// Attribute parsing automaton.
///
@ -185,7 +56,7 @@ pub trait AttrParseState: ClosedParseState {
/// The default is [`Infallible`],
/// meaning such conversion cannot fail and [`From`] may be used in
/// place of [`TryFrom`].
type ValueError: Diagnostic = Infallible;
type ValueError: Diagnostic + PartialEq = Infallible;
/// Begin attribute parsing within the context of the provided element.
///

View File

@ -19,6 +19,7 @@
use super::*;
use crate::{
diagnose::AnnotatedSpan,
parse::{ParseError, ParseState, Parsed, Parser, TokenStream},
span::dummy::*,
sym::SymbolId,
@ -28,7 +29,7 @@ use crate::{
st::qname::*,
},
};
use std::assert_matches::assert_matches;
use std::{assert_matches::assert_matches, error::Error, fmt::Display};
const SE: OpenSpan = OpenSpan(S1.offset_add(100).unwrap(), 0);
@ -373,7 +374,7 @@ fn mixed_some_optional_missing() {
mod required {
use super::*;
use crate::sym::st;
use crate::{sym::st, xir::EleSpan};
attr_parse! {
struct ReqMissingState -> ReqMissing {

View File

@ -19,23 +19,16 @@
//! Element parser generator for parsing of [XIRF](super::super::flat).
use arrayvec::ArrayVec;
use std::{
error::Error,
fmt::{Debug, Display},
marker::PhantomData,
};
use crate::{
diagnose::Diagnostic,
diagnostic_panic,
fmt::{DisplayWrapper, TtQuote},
parse::{
ClosedParseState, Context, ParseState, Transition, TransitionResult,
},
span::Span,
xir::{Prefix, QName},
};
use arrayvec::ArrayVec;
use std::fmt::{Debug, Display};
#[cfg(doc)]
use crate::{ele_parse, parse::Parser};
@ -223,6 +216,17 @@ impl Display for NodeMatcher {
}
}
pub trait Nt: Debug {
fn matcher() -> NodeMatcher;
}
/// Sum nonterminal.
///
/// This trait is used internally by the [`ele_parse!`] parser-generator.
pub trait SumNt: Debug {
fn fmt_matches_top(f: &mut std::fmt::Formatter) -> std::fmt::Result;
}
#[macro_export]
macro_rules! ele_parse {
(
@ -532,18 +536,11 @@ macro_rules! ele_parse {
}
impl $nt {
/// Matcher describing the node recognized by this parser.
#[allow(dead_code)] // used by sum parser
#[inline]
fn matcher() -> crate::xir::parse::NodeMatcher {
crate::xir::parse::NodeMatcher::from($qname)
}
/// Whether the given QName would be matched by any of the
/// parsers associated with this type.
#[inline]
fn matches(qname: crate::xir::QName) -> bool {
Self::matcher().matches(qname)
<Self as crate::xir::parse::Nt>::matcher().matches(qname)
}
/// Number of
@ -556,7 +553,7 @@ macro_rules! ele_parse {
1
}
/// Format [`Self::matcher`] for display.
/// Format matcher for display.
///
/// This value may be rendered singularly or as part of a
/// list of values joined together by Sum NTs.
@ -574,10 +571,11 @@ macro_rules! ele_parse {
) -> std::fmt::Result {
use crate::{
fmt::ListDisplayWrapper,
xir::fmt::EleSumList,
xir::{fmt::EleSumList, parse::Nt},
};
EleSumList::fmt_nth(n, *i, &Self::matcher(), f)?;
let matcher = &<Self as Nt>::matcher();
EleSumList::fmt_nth(n, *i, matcher, f)?;
*i += 1;
Ok(())
@ -664,11 +662,22 @@ macro_rules! ele_parse {
}
}
impl crate::xir::parse::Nt for $nt {
/// Matcher describing the node recognized by this parser.
#[inline]
fn matcher() -> crate::xir::parse::NodeMatcher {
crate::xir::parse::NodeMatcher::from($qname)
}
}
impl std::fmt::Display for $nt {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::{
fmt::{DisplayWrapper, TtQuote},
xir::fmt::{TtOpenXmlEle, TtCloseXmlEle},
xir::{
fmt::{TtOpenXmlEle, TtCloseXmlEle},
parse::Nt,
},
};
match self {
@ -676,7 +685,7 @@ macro_rules! ele_parse {
| Self::NonPreemptableExpecting_ => write!(
f,
"expecting opening tag {}",
TtOpenXmlEle::wrap(Self::matcher()),
TtOpenXmlEle::wrap(<Self as Nt>::matcher()),
),
Self::RecoverEleIgnore_(name, _, _)
| Self::RecoverEleIgnoreClosed_(name, _) => write!(
@ -685,7 +694,7 @@ macro_rules! ele_parse {
with unexpected name {given} \
(expected {expected})",
given = TtQuote::wrap(name),
expected = TtQuote::wrap(Self::matcher()),
expected = TtQuote::wrap(<Self as Nt>::matcher()),
),
Self::CloseRecoverIgnore_((qname, _, depth), _) => write!(
f,
@ -710,7 +719,7 @@ macro_rules! ele_parse {
Self::Closed_(None, _) => write!(
f,
"skipped parsing element {}",
TtQuote::wrap(Self::matcher()),
TtQuote::wrap(<Self as Nt>::matcher()),
),
$(
// TODO: A better description.
@ -726,101 +735,10 @@ macro_rules! ele_parse {
}
}
#[derive(Debug, PartialEq)]
$vis enum [<$nt Error_>] {
/// An element was expected,
/// but the name of the element was unexpected.
UnexpectedEle(crate::xir::QName, crate::span::Span),
/// Unexpected input while expecting an end tag for this
/// element.
///
/// The span corresponds to the opening tag.
CloseExpected(
crate::xir::QName,
crate::xir::OpenSpan,
crate::xir::flat::XirfToken<crate::xir::flat::RefinedText>,
),
Attrs(crate::xir::parse::AttrParseError<[<$nt AttrsState_>]>),
}
impl From<crate::xir::parse::AttrParseError<[<$nt AttrsState_>]>>
for [<$nt Error_>]
{
fn from(
e: crate::xir::parse::AttrParseError<[<$nt AttrsState_>]>
) -> Self {
[<$nt Error_>]::Attrs(e)
}
}
impl std::error::Error for [<$nt Error_>] {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
// TODO
None
}
}
impl std::fmt::Display for [<$nt Error_>] {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::{
fmt::{DisplayWrapper, TtQuote},
xir::fmt::{TtOpenXmlEle, TtCloseXmlEle},
};
match self {
Self::UnexpectedEle(name, _) => write!(
f,
"unexpected {unexpected} (expecting {expected})",
unexpected = TtOpenXmlEle::wrap(name),
expected = TtOpenXmlEle::wrap($nt::matcher()),
),
Self::CloseExpected(qname, _, tok) => write!(
f,
"expected {}, but found {}",
TtCloseXmlEle::wrap(qname),
TtQuote::wrap(tok)
),
Self::Attrs(e) => std::fmt::Display::fmt(e, f),
}
}
}
impl crate::diagnose::Diagnostic for [<$nt Error_>] {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use crate::{
diagnose::Annotate,
fmt::{DisplayWrapper, TtQuote},
parse::Token,
xir::{
EleSpan,
fmt::TtCloseXmlEle,
},
};
match self {
Self::UnexpectedEle(_, ospan) => ospan.error(
format!(
"expected {ele_name} here",
ele_name = TtQuote::wrap($nt::matcher())
)
).into(),
Self::CloseExpected(qname, ospan, tok) => vec![
ospan.span().note("element starts here"),
tok.span().error(format!(
"expected {}",
TtCloseXmlEle::wrap(qname),
)),
],
Self::Attrs(e) => e.describe(),
}
}
}
// Used by superstate sum type.
#[doc(hidden)]
type [<$nt Error_>] =
crate::xir::parse::NtError<$nt, [<$nt AttrsState_>]>;
impl crate::parse::ParseState for $nt {
type Token = crate::xir::flat::XirfToken<
@ -1744,61 +1662,5 @@ macro_rules! ele_parse {
}
}
/// Sum nonterminal.
///
/// This trait is used internally by the [`ele_parse!`] parser-generator.
pub trait SumNt: Debug {
fn fmt_matches_top(f: &mut std::fmt::Formatter) -> std::fmt::Result;
}
/// Error during parsing of a sum nonterminal.
#[derive(Debug, PartialEq)]
pub enum SumNtError<NT: SumNt> {
UnexpectedEle(QName, Span, PhantomData<NT>),
}
impl<NT: SumNt> Error for SumNtError<NT> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl<NT: SumNt> Display for SumNtError<NT> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::xir::fmt::TtOpenXmlEle;
match self {
Self::UnexpectedEle(qname, _, _) => {
write!(f, "unexpected {}", TtOpenXmlEle::wrap(qname))
}
}
}
}
impl<NT: SumNt> Diagnostic for SumNtError<NT> {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use crate::{diagnose::Annotate, fmt::DisplayFn};
// Note that we should place expected values in the help
// footnote rather than the span label because it can
// get rather long.
// Maybe in the future the diagnostic renderer can be
// smart about that based on the terminal width and
// automatically move into the footer.
match self {
Self::UnexpectedEle(qname, span, _) => span
.error(format!(
"element {name} cannot appear here",
name = TtQuote::wrap(qname),
))
.with_help(format!(
"expecting {}",
DisplayFn(NT::fmt_matches_top)
))
.into(),
}
}
}
#[cfg(test)]
mod test;

View File

@ -0,0 +1,287 @@
// XIR element parser generator errors
//
// 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/>.
//! Parsing errors from the parser generator.
use core::fmt::Debug;
use std::{
error::Error,
fmt::{Display, Formatter},
marker::PhantomData,
};
use crate::{
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
fmt::{DisplayFn, DisplayWrapper, ListDisplayWrapper, TtQuote},
span::Span,
xir::{
attr::Attr,
flat::{RefinedText, XirfToken},
fmt::XmlAttrList,
EleSpan, OpenSpan, QName,
},
};
use super::{AttrParseState, Nt, SumNt};
#[derive(Debug, PartialEq)]
pub enum NtError<NT: Nt, A: AttrParseState> {
/// An element was expected,
/// but the name of the element was unexpected.
UnexpectedEle(QName, Span),
/// Unexpected input while expecting an end tag for this
/// element.
///
/// The span corresponds to the opening tag.
CloseExpected(QName, OpenSpan, XirfToken<RefinedText>),
Attrs(AttrParseError<A>, PhantomData<NT>),
}
impl<NT: Nt, A: AttrParseState> From<AttrParseError<A>> for NtError<NT, A> {
fn from(e: AttrParseError<A>) -> Self {
Self::Attrs(e, PhantomData::default())
}
}
impl<NT: Nt, A: AttrParseState> Error for NtError<NT, A> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
// TODO
None
}
}
impl<NT: Nt, A: AttrParseState> Display for NtError<NT, A> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
use crate::xir::fmt::{TtCloseXmlEle, TtOpenXmlEle};
match self {
Self::UnexpectedEle(name, _) => write!(
f,
"unexpected {unexpected} (expecting {expected})",
unexpected = TtOpenXmlEle::wrap(name),
expected = TtOpenXmlEle::wrap(NT::matcher()),
),
Self::CloseExpected(qname, _, tok) => write!(
f,
"expected {}, but found {}",
TtCloseXmlEle::wrap(qname),
TtQuote::wrap(tok)
),
Self::Attrs(e, _) => Display::fmt(e, f),
}
}
}
impl<NT: Nt, A: AttrParseState> Diagnostic for NtError<NT, A> {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use crate::{parse::Token, xir::fmt::TtCloseXmlEle};
match self {
Self::UnexpectedEle(_, ospan) => ospan
.error(format!(
"expected {ele_name} here",
ele_name = TtQuote::wrap(NT::matcher())
))
.into(),
Self::CloseExpected(qname, ospan, tok) => vec![
ospan.span().note("element starts here"),
tok.span()
.error(format!("expected {}", TtCloseXmlEle::wrap(qname),)),
],
Self::Attrs(e, _) => e.describe(),
}
}
}
/// Error during parsing of a sum nonterminal.
#[derive(Debug, PartialEq)]
pub enum SumNtError<NT: SumNt> {
UnexpectedEle(QName, Span, PhantomData<NT>),
}
impl<NT: SumNt> Error for SumNtError<NT> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl<NT: SumNt> Display for SumNtError<NT> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::xir::fmt::TtOpenXmlEle;
match self {
Self::UnexpectedEle(qname, _, _) => {
write!(f, "unexpected {}", TtOpenXmlEle::wrap(qname))
}
}
}
}
impl<NT: SumNt> Diagnostic for SumNtError<NT> {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
// Note that we should place expected values in the help
// footnote rather than the span label because it can
// get rather long.
// Maybe in the future the diagnostic renderer can be
// smart about that based on the terminal width and
// automatically move into the footer.
match self {
Self::UnexpectedEle(qname, span, _) => span
.error(format!(
"element {name} cannot appear here",
name = TtQuote::wrap(qname),
))
.with_help(format!(
"expecting {}",
DisplayFn(NT::fmt_matches_top)
))
.into(),
}
}
}
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),
/// 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),
}
impl<S: AttrParseState> Display for AttrParseError<S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::MissingRequired(st) => {
let ele_name = st.element_name();
write!(f, "element `{ele_name}` missing required ")?;
XmlAttrList::fmt(&st.required_missing(), f)
}
Self::UnexpectedAttr(attr, ele_name) => {
write!(
f,
"unexpected attribute `{attr}` for \
element `{ele_name}`"
)
}
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))
}
}
}
}
impl<S: AttrParseState> Error for AttrParseError<S> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
impl<S: AttrParseState> Diagnostic for AttrParseError<S> {
fn describe(&self) -> Vec<AnnotatedSpan> {
match self {
Self::MissingRequired(st) => st
.element_span()
.tag_span()
.error(format!(
"missing required {}",
XmlAttrList::wrap(&st.required_missing()),
))
.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(),
}
}
}