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-7145main
parent
5078bd8bda
commit
212ca06efe
|
@ -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};
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue