tamer: xir::parse::ele: Handle Close when expecting Open

I'm starting to clean up some TODOs, and this was a glaring one causing
panics when encountered.  The recovery for this is simple, because we have
no choice: just stop parsing; leave it to the next lowering operation(s) to
complain that we didn't provide what was necessary.  They'll have to,
anyway, since templates mean that NIR cannot ever have enough information to
guarantee that a document is well-formed, relative to what would expand from
the template.

DEV-7145
main
Mike Gerwitz 2022-08-17 14:49:34 -04:00
parent 4c86c5b63c
commit b31ebc00a7
2 changed files with 284 additions and 5 deletions

View File

@ -532,7 +532,7 @@ macro_rules! ele_parse {
/// complete.
Closed_(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
Option<crate::xir::QName>,
crate::span::Span
),
}
@ -713,11 +713,17 @@ macro_rules! ele_parse {
"expecting closing element {} at depth {depth}",
TtCloseXmlEle::wrap(qname)
),
Self::Closed_(_, qname, _) => write!(
Self::Closed_(_, Some(qname), _) => write!(
f,
"done parsing element {}",
TtQuote::wrap(qname),
),
// Should only happen on an unexpected `Close`.
Self::Closed_(_, None, _) => write!(
f,
"skipped parsing element {}",
TtQuote::wrap(Self::matcher()),
),
$(
// TODO: A better description.
Self::$ntref(_) => {
@ -738,6 +744,15 @@ macro_rules! ele_parse {
/// but the name of the element was unexpected.
UnexpectedEle_(crate::xir::QName, crate::span::Span),
/// A child element was expected,
/// but instead the parent element was closed.
///
/// To be clear:
/// the child element is _us_,
/// so the closing parent element is at a depth one
/// higher than we are parsing.
UnexpectedClose_(Option<crate::xir::QName>, crate::span::Span),
/// Unexpected input while expecting an end tag for this
/// element.
///
@ -782,12 +797,35 @@ macro_rules! ele_parse {
unexpected = TtOpenXmlEle::wrap(name),
expected = TtOpenXmlEle::wrap($nt::matcher()),
),
Self::UnexpectedClose_(oqname, _) => {
// Unlike Sum NTs,
// since our `expected` string is small,
// we can include it in the error summary.
match oqname {
Some(qname) => write!(
f,
"unexpected {unexpected} \
(expecting {expected})",
unexpected = TtCloseXmlEle::wrap(qname),
expected = TtCloseXmlEle::wrap($nt::matcher()),
),
None => write!(
f,
"unexpected close of element \
(expecting {})",
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),
}
}
@ -810,6 +848,15 @@ macro_rules! ele_parse {
)
).into(),
Self::UnexpectedClose_(_, span) => {
span
.error(format!(
"expecting {}",
TtCloseXmlEle::wrap($nt::matcher()),
))
.into()
}
Self::CloseExpected_(qname, span, tok) => vec![
span.note("element starts here"),
tok.span().error(format!(
@ -896,6 +943,28 @@ macro_rules! ele_parse {
)).incomplete()
},
// `Close` when expecting an element.
// Note that the above repetition check will prevent
// this error when repeating.
(
Expecting_(cfg) | NonPreemptableExpecting_(cfg),
XirfToken::Close(oqname, cspan, depth)
) => {
// We have no choice but to close,
// but downstream IRs lowering probably won't
// be happy about it.
Transition(Closed_(cfg, oqname, cspan.tag_span())).err(
// The error span should represent the tag,
// since the fact that we encountered a
// `Close` at all is the problem.
[<$nt Error_>]::UnexpectedClose_(
oqname, cspan.tag_span()
)
).with_lookahead(
XirfToken::Close(oqname, cspan, depth)
)
}
(
Expecting_(cfg) | NonPreemptableExpecting_(cfg),
XirfToken::Open(qname, span, depth)
@ -1026,7 +1095,7 @@ macro_rules! ele_parse {
$(
let $close_span = span;
)?
$closemap.transition(Closed_(cfg, qname, span.tag_span()))
$closemap.transition(Closed_(cfg, Some(qname), span.tag_span()))
},
(ExpectClose_(meta @ (_, qname, otspan, _)), unexpected_tok) => {
@ -1247,6 +1316,10 @@ macro_rules! ele_parse {
#[derive(Debug, PartialEq)]
$vis enum [<$nt Error_>] {
UnexpectedEle_(crate::xir::QName, crate::span::Span),
/// A child element was expected,
/// but instead the parent element was closed.
UnexpectedClose_(Option<crate::xir::QName>, crate::span::Span),
}
impl std::error::Error for [<$nt Error_>] {
@ -1260,13 +1333,29 @@ macro_rules! ele_parse {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::{
fmt::DisplayWrapper,
xir::fmt::TtOpenXmlEle,
xir::fmt::{TtOpenXmlEle, TtCloseXmlEle},
};
match self {
Self::UnexpectedEle_(qname, _) => {
write!(f, "unexpected {}", TtOpenXmlEle::wrap(qname))
},
Self::UnexpectedClose_(oqname, _) => {
// Don't include expected in the error summary
// because it can be really large.
match oqname {
Some(qname) => write!(
f,
"unexpected {}",
TtCloseXmlEle::wrap(qname)
),
None => write!(
f,
"unexpected close of element",
),
}
},
}
}
}
@ -1278,6 +1367,12 @@ macro_rules! ele_parse {
fmt::{DisplayWrapper, TtQuote, 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
@ -1291,6 +1386,16 @@ macro_rules! ele_parse {
))
.into()
},
Self::UnexpectedClose_(_, span) => {
span
.error("element was closed prematurely")
.with_help(format!(
"expecting {}",
DisplayFn($nt::fmt_matches_top)
))
.into()
}
}
}
}
@ -1314,6 +1419,7 @@ macro_rules! ele_parse {
xir::{
flat::XirfToken,
parse::EleParseCfg,
EleSpan,
},
};
@ -1392,11 +1498,32 @@ macro_rules! ele_parse {
tok
) if cfg.repeat => Transition(Done_).dead(tok),
// `Close` when expecting an element.
// Note that the above repetition check will prevent
// this error when repeating.
(
Expecting_(..) | NonPreemptableExpecting_(..),
XirfToken::Close(oqname, cspan, depth)
) => {
// We have no choice but to close,
// but downstream IRs lowering probably won't
// be happy about it.
Transition(Done_).err(
// The error span should represent the tag,
// since the fact that we encountered a
// `Close` at all is the problem.
[<$nt Error_>]::UnexpectedClose_(
oqname, cspan.tag_span()
)
).with_lookahead(
XirfToken::Close(oqname, cspan, depth)
)
}
(
Expecting_(cfg) | NonPreemptableExpecting_(cfg),
XirfToken::Open(qname, span, depth)
) => {
use crate::xir::EleSpan;
Transition(RecoverEleIgnore_(cfg, qname, span, depth)).err(
// Use name span rather than full `OpenSpan`
// since it's specifically the name that

View File

@ -1210,6 +1210,158 @@ fn sum_nonterminal_as_child_element() {
);
}
// Parent closes before expected (non-Sum) NT is satisfied.
#[test]
fn nonterminal_unexpected_close() {
#[derive(Debug, PartialEq, Eq)]
enum Foo {
Open,
Child,
Close,
}
impl crate::parse::Object for Foo {}
// QNames don't matter as long as they are unique.
const QN_ROOT: QName = QN_PACKAGE;
const QN_CHILD: QName = QN_PACKAGE;
ele_parse! {
enum Sut;
type Object = Foo;
Root := QN_PACKAGE {
@ {} => Foo::Open,
/ => Foo::Close,
Child,
};
Child := QN_CHILD {
@ {} => Foo::Child,
};
}
let toks = vec![
XirfToken::Open(QN_ROOT, OpenSpan(S1, N), Depth(0)),
// We're expecting `Child`...but nope.
XirfToken::Close(Some(QN_ROOT), CloseSpan(S2, N), Depth(0)),
];
use Parsed::*;
let mut sut = Sut::parse(toks.into_iter());
// The first two iterations are expected.
assert_eq!(sut.next(), Some(Ok(Incomplete))); // [Root] Root Open
assert_eq!(sut.next(), Some(Ok(Object(Foo::Open))),); // [Root] Root Close (<LA)
// But once we encounter the token of lookahead,
// which is `Close`,
// we're in error,
// since we expected `A|B`.
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
err,
// TODO: This references generated identifiers.
ParseError::StateError(SutError_::Child(
ChildError_::UnexpectedClose_(
Some(QN_ROOT),
CloseSpan(S2, N).tag_span(),
)
)),
);
// Recovery should complete AB despite our objections,
// and the token of lookahead should allow the root to close
// successfully.
assert_eq!(
Ok(vec![
Object(Foo::Close), // [Root] Root Close (<LA)
]),
sut.collect(),
);
}
// Parent closes before expected Sum NT is satisfied.
// Same idea as the above test.
#[test]
fn nonterminal_unexpected_close_sum() {
#[derive(Debug, PartialEq, Eq)]
enum Foo {
Open,
Child,
Close,
}
impl crate::parse::Object for Foo {}
// QNames don't matter as long as they are unique.
const QN_ROOT: QName = QN_PACKAGE;
const QN_A: QName = QN_PACKAGE;
const QN_B: QName = QN_CLASSIFY;
ele_parse! {
enum Sut;
type Object = Foo;
Root := QN_PACKAGE {
@ {} => Foo::Open,
/ => Foo::Close,
AB,
};
AB := (A | B);
A := QN_A {
@ {} => Foo::Child,
};
B := QN_B {
@ {} => Foo::Child,
};
}
let toks = vec![
XirfToken::Open(QN_ROOT, OpenSpan(S1, N), Depth(0)),
// We're expecting `A|B`...but nope.
XirfToken::Close(Some(QN_ROOT), CloseSpan(S2, N), Depth(0)),
];
use Parsed::*;
let mut sut = Sut::parse(toks.into_iter());
// The first two iterations are expected.
assert_eq!(sut.next(), Some(Ok(Incomplete))); // [Root] Root Open
assert_eq!(sut.next(), Some(Ok(Object(Foo::Open))),); // [Root] Root Close (<LA)
// But once we encounter the token of lookahead,
// which is `Close`,
// we're in error,
// since we expected `A|B`.
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
err,
// TODO: This references generated identifiers.
ParseError::StateError(SutError_::AB(ABError_::UnexpectedClose_(
Some(QN_ROOT),
CloseSpan(S2, N).tag_span(),
))),
);
// Recovery should complete AB despite our objections,
// and the token of lookahead should allow the root to close
// successfully.
assert_eq!(
Ok(vec![
Object(Foo::Close), // [Root] Root Close (<LA)
]),
sut.collect(),
);
}
#[test]
fn sum_nonterminal_error_recovery() {
#[derive(Debug, PartialEq, Eq)]