tamer: xir::parse::ele: Diagnostic output

The only additional information needed was opening spans so that we can
provide useful information regarding closing tags.

This uses a generic Span in place of {Open,Close}Span because the latter
wasn't necessary, but more descriptive types would be nice; it may be
beneficial later on to introduce newtypes for each of the span generated by
{Open,Close}Span.

DEV-7145
main
Mike Gerwitz 2022-07-20 12:17:15 -04:00
parent ce765d3b56
commit c856fd72d9
2 changed files with 128 additions and 36 deletions

View File

@ -193,13 +193,13 @@ macro_rules! ele_parse {
/// may be a child element,
/// but it may be text,
/// for example.
CloseRecoverIgnore_(Depth, crate::span::Span),
CloseRecoverIgnore_((crate::span::Span, Depth), crate::span::Span),
/// Parsing element attributes.
Attrs_(Depth, [<$nt AttrsState_>]),
Attrs_((crate::span::Span, Depth), [<$nt AttrsState_>]),
$(
$ntref(Depth, $ntref),
$ntref((crate::span::Span, Depth), $ntref),
)*
ExpectClose_(Depth, ()),
ExpectClose_((crate::span::Span, Depth), ()),
/// Closing tag found and parsing of the element is
/// complete.
Closed_(crate::span::Span),
@ -238,7 +238,7 @@ macro_rules! ele_parse {
given = TtQuote::wrap(name),
expected = TtQuote::wrap($qname),
),
Self::CloseRecoverIgnore_(depth, _) => write!(
Self::CloseRecoverIgnore_((_, depth), _) => write!(
f,
"attempting to recover by ignoring input \
until the expected end tag {expected} \
@ -247,7 +247,7 @@ macro_rules! ele_parse {
),
Self::Attrs_(_, sa) => std::fmt::Display::fmt(sa, f),
Self::ExpectClose_(depth, _) => write!(
Self::ExpectClose_((_, depth), _) => write!(
f,
"expecting closing element {} at depth {depth}",
TtCloseXmlEle::wrap($qname)
@ -271,10 +271,15 @@ macro_rules! ele_parse {
/// 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.
CloseExpected_(crate::xir::flat::XirfToken),
///
/// The span corresponds to the opening tag.
CloseExpected_(crate::span::Span, crate::xir::flat::XirfToken),
Attrs_(crate::xir::parse::AttrParseError<[<$nt AttrsState_>]>),
$(
$ntref([<$ntref Error_>]),
)*
@ -313,10 +318,13 @@ macro_rules! ele_parse {
};
match self {
Self::UnexpectedEle_(name, _) => {
write!(f, "unexpected {}", TtOpenXmlEle::wrap(name))
}
Self::CloseExpected_(tok) => write!(
Self::UnexpectedEle_(name, _) => write!(
f,
"unexpected {unexpected} (expecting {expected})",
unexpected = TtOpenXmlEle::wrap(name),
expected = TtOpenXmlEle::wrap($qname),
),
Self::CloseExpected_(_, tok) => write!(
f,
"expected {}, but found {}",
TtCloseXmlEle::wrap($qname),
@ -332,7 +340,35 @@ macro_rules! ele_parse {
impl crate::diagnose::Diagnostic for [<$nt Error_>] {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
todo!()
use crate::{
diagnose::Annotate,
fmt::{DisplayWrapper, TtQuote},
parse::Token,
xir::fmt::{TtCloseXmlEle},
};
match self {
Self::UnexpectedEle_(_, ospan) => ospan.error(
format!(
"expected {ele_name} here",
ele_name = TtQuote::wrap($qname)
)
).into(),
Self::CloseExpected_(span, tok) => vec![
span.note("element starts here"),
tok.span().error(format!(
"expected {}",
TtCloseXmlEle::wrap($qname),
)),
],
Self::Attrs_(e) => e.describe(),
$(
Self::$ntref(e) => e.describe(),
)*
}
}
}
@ -366,16 +402,20 @@ macro_rules! ele_parse {
Expecting_,
XirfToken::Open(qname, span, depth)
) if qname == $qname => {
Transition(Attrs_(depth, parse_attrs(qname, span)))
.incomplete()
Transition(Attrs_(
(span.tag_span(), depth),
parse_attrs(qname, span)
)).incomplete()
},
(
Closed_(..),
XirfToken::Open(qname, span, depth)
) if cfg.repeat && qname == $qname => {
Transition(Attrs_(depth, parse_attrs(qname, span)))
.incomplete()
Transition(Attrs_(
(span.tag_span(), depth),
parse_attrs(qname, span)
)).incomplete()
},
(Expecting_, XirfToken::Open(qname, span, depth)) => {
@ -391,11 +431,11 @@ macro_rules! ele_parse {
Transition(RecoverEleIgnoreClosed_(qname, span)).incomplete()
},
(Attrs_(depth, sa), tok) => {
(Attrs_(meta, sa), tok) => {
sa.delegate_until_obj(
tok,
EmptyContext,
|sa| Transition(Attrs_(depth, sa)),
|sa| Transition(Attrs_(meta, sa)),
|| unreachable!("see ParseState::delegate_until_obj dead"),
|#[allow(unused_variables)] sa, attrs| {
let obj = match attrs {
@ -415,7 +455,7 @@ macro_rules! ele_parse {
},
};
Transition($ntfirst(depth, Default::default()))
Transition($ntfirst(meta, Default::default()))
.ok(obj)
}
)
@ -435,7 +475,8 @@ macro_rules! ele_parse {
// XIRF ensures proper nesting,
// so we do not need to check the element name.
(
ExpectClose_(depth, ()) | CloseRecoverIgnore_(depth, _),
ExpectClose_((_, depth), ())
| CloseRecoverIgnore_((_, depth), _),
XirfToken::Close(_, span, tok_depth)
) if tok_depth == depth => {
$(
@ -444,11 +485,11 @@ macro_rules! ele_parse {
$closemap.transition(Closed_(span.tag_span()))
},
(ExpectClose_(depth, ()), unexpected_tok) => {
(ExpectClose_(meta @ (otspan, _), ()), unexpected_tok) => {
use crate::parse::Token;
Transition(
CloseRecoverIgnore_(depth, unexpected_tok.span())
).err([<$nt Error_>]::CloseExpected_(unexpected_tok))
CloseRecoverIgnore_(meta, unexpected_tok.span())
).err([<$nt Error_>]::CloseExpected_(otspan, unexpected_tok))
}
// We're still in recovery,
@ -584,7 +625,32 @@ macro_rules! ele_parse {
impl crate::diagnose::Diagnostic for [<$nt Error_>] {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
todo!()
use crate::{
diagnose::Annotate,
fmt::{DisplayWrapper, ListDisplayWrapper, TtQuote},
xir::fmt::OpenEleSumList,
};
let ntrefs = [
$(
$ntref::qname(),
)*
];
let expected = OpenEleSumList::wrap(&ntrefs);
match self {
Self::UnexpectedEle_(qname, span) => {
span.error(format!(
"element {name} cannot appear here \
(expecting {expected})",
name = TtQuote::wrap(qname),
)).into()
},
$(
Self::$ntref(e) => e.describe(),
)*
}
}
}

View File

@ -34,6 +34,7 @@
use crate::{
convert::ExpectInto,
diagnose::Diagnostic,
parse::{Object, ParseError, ParseState, Parsed},
span::{Span, DUMMY_SPAN},
sym::SymbolId,
@ -270,15 +271,20 @@ fn unexpected_element() {
// was encountered
// (which was expected),
// but to the fact that the name was not the one expected.
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
// TODO: This references generated identifiers.
Some(Err(ParseError::StateError(SutError_::UnexpectedEle_(
ParseError::StateError(SutError_::UnexpectedEle_(
unexpected,
span.name_span()
)))),
sut.next(),
)),
err,
);
// The diagnostic should describe the name of the element as being
// invalid.
assert_eq!(err.describe()[0].span(), span.name_span());
// We should have now entered a recovery mode whereby we discard
// input until we close the element that introduced the error.
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // Attr
@ -500,14 +506,18 @@ fn child_error_and_recovery() {
// The token should be consumed and returned in the error,
// _not_ produced as a token of lookahead,
// since we do not want to reprocess bad input.
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
// TODO: This references generated identifiers.
Some(Err(ParseError::StateError(SutError_::ChildA(
ParseError::StateError(SutError_::ChildA(
ChildAError_::UnexpectedEle_(unexpected, span.name_span())
)))),
sut.next(),
)),
err,
);
// Diagnostic message should be delegated to the child.
assert_eq!(err.describe()[0].span(), span.name_span());
// The next token is the self-closing `Close` for the unexpected opening
// tag.
// Since we are in recovery,
@ -607,14 +617,25 @@ fn child_error_and_recovery_at_close() {
// The token should be consumed and returned in the error,
// _not_ produced as a token of lookahead,
// since we do not want to reprocess bad input.
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
// TODO: This references generated identifiers.
Some(Err(ParseError::StateError(SutError_::CloseExpected_(
ParseError::StateError(SutError_::CloseExpected_(
OpenSpan(S1, N).tag_span(),
XirfToken::Open(unexpected_a, span_a, Depth(1)),
)))),
sut.next(),
)),
err,
);
// The diagnostic information should include a reference to where the
// element was opened
// (so that the user understands what needs closing),
// followed by the span of the token in error
// (which naturally comes after the opening tag).
let desc = err.describe();
assert_eq!(desc[0].span(), S1); // Span of opening tag we want closed
assert_eq!(desc[1].span(), span_a.span()); // Span of error
// The recovery state must not be in an accepting state,
// because we didn't close at the root depth yet.
let (mut sut, _) =
@ -833,15 +854,19 @@ fn sum_nonterminal_error_recovery() {
// was encountered
// (which was expected),
// but to the fact that the name was not the one expected.
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
sut.next(),
err,
// TODO: This references generated identifiers.
Some(Err(ParseError::StateError(SutError_::UnexpectedEle_(
ParseError::StateError(SutError_::UnexpectedEle_(
unexpected,
OpenSpan(S1, N).name_span(),
)))),
)),
);
// Diagnostic message should describe the name of the element.
assert_eq!(err.describe()[0].span(), OpenSpan(S1, N).name_span());
// We should have now entered a recovery mode whereby we discard
// input until we close the element that introduced the error.
assert_eq!(sut.next(), Some(Ok(Parsed::Incomplete))); // Open child
@ -1051,6 +1076,7 @@ fn child_repetition_invalid_tok_dead() {
next(),
// TODO: This references generated identifiers.
Some(Err(ParseError::StateError(SutError_::CloseExpected_(
OpenSpan(S1, N).tag_span(),
XirfToken::Open(unexpected, OpenSpan(S2, N), Depth(1)),
)))),
);