tamer: xir::parse::ele: Store matching QName on NS match

When we match a QName against a namespace, we ought to store the matching
QName to use (a) in error messages and (b) to make available as a
binding.  The former is necessary for sensible errors (rather than saying
that it's e.g. expecting a closing `t:*`) and the latter is necessary for
e.g. getting the template name out of `t:foo`.

DEV-7145
main
Mike Gerwitz 2022-08-11 01:15:44 -04:00
parent 8cb03d8d16
commit 67ee914505
4 changed files with 98 additions and 35 deletions

View File

@ -521,6 +521,7 @@ pub mod st {
L_FUNC: cid "func",
L_GEN: cid "gen",
L_GENERATED: cid "generated",
L_GT: cid "gt",
L_ID: cid "id",
L_INTEGER: cid "integer",
L_ISOVERRIDE: cid "isoverride",

View File

@ -401,7 +401,8 @@ macro_rules! ele_parse {
};
(@!ele_dfn_body <$objty:ty, $($evty:ty)?>
$vis:vis $super:ident $nt:ident $qname:ident ($($open_span:ident)?)
$vis:vis $super:ident $nt:ident $qname:ident
($($qname_matched:pat, $open_span:pat)?)
// Attribute definition special form.
@ {
@ -484,6 +485,7 @@ macro_rules! ele_parse {
CloseRecoverIgnore_(
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::flat::Depth
),
@ -493,6 +495,7 @@ macro_rules! ele_parse {
Attrs_(
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::flat::Depth
),
@ -502,6 +505,7 @@ macro_rules! ele_parse {
$ntref(
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::flat::Depth
),
@ -510,13 +514,18 @@ macro_rules! ele_parse {
ExpectClose_(
(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span,
crate::xir::flat::Depth
),
),
/// Closing tag found and parsing of the element is
/// complete.
Closed_(crate::xir::parse::EleParseCfg, crate::span::Span),
Closed_(
crate::xir::parse::EleParseCfg,
crate::xir::QName,
crate::span::Span
),
}
impl From<crate::xir::parse::EleParseCfg> for $nt {
@ -539,9 +548,9 @@ macro_rules! ele_parse {
#[allow(dead_code)] // used by text special form
fn child_depth(&self) -> Option<crate::xir::flat::Depth> {
match self {
$ntfirst((_, _, depth)) => Some(depth.child_depth()),
$ntfirst((_, _, _, depth)) => Some(depth.child_depth()),
$(
$ntnext((_, _, depth)) => Some(depth.child_depth()),
$ntnext((_, _, _, depth)) => Some(depth.child_depth()),
)*
_ => None,
}
@ -570,26 +579,24 @@ macro_rules! ele_parse {
given = TtQuote::wrap(name),
expected = TtQuote::wrap(Self::matcher()),
),
Self::CloseRecoverIgnore_((_, _, depth), _) => write!(
Self::CloseRecoverIgnore_((_, qname, _, depth), _) => write!(
f,
"attempting to recover by ignoring input \
until the expected end tag {expected} \
at depth {depth}",
expected = TtCloseXmlEle::wrap(Self::matcher()),
expected = TtCloseXmlEle::wrap(qname),
),
Self::Attrs_(_, sa) => std::fmt::Display::fmt(sa, f),
Self::ExpectClose_((_, _, depth)) => write!(
Self::ExpectClose_((_, qname, _, depth)) => write!(
f,
"expecting closing element {} at depth {depth}",
// TODO: Actual closing tag name
// (this may be a prefix).
TtCloseXmlEle::wrap(Self::matcher())
TtCloseXmlEle::wrap(qname)
),
Self::Closed_(_, _cfg) => write!(
Self::Closed_(_, qname, _) => write!(
f,
"done parsing element {}",
TtQuote::wrap(Self::matcher())
TtQuote::wrap(qname),
),
$(
// TODO: A better description.
@ -616,6 +623,7 @@ macro_rules! ele_parse {
///
/// The span corresponds to the opening tag.
CloseExpected_(
crate::xir::QName,
crate::span::Span,
crate::xir::flat::XirfToken<crate::xir::flat::RefinedText>,
),
@ -654,11 +662,10 @@ macro_rules! ele_parse {
unexpected = TtOpenXmlEle::wrap(name),
expected = TtOpenXmlEle::wrap($nt::matcher()),
),
Self::CloseExpected_(_, tok) => write!(
Self::CloseExpected_(qname, _, tok) => write!(
f,
"expected {}, but found {}",
// TODO: Actual close name.
TtCloseXmlEle::wrap($nt::matcher()),
TtCloseXmlEle::wrap(qname),
TtQuote::wrap(tok)
),
Self::Attrs_(e) => std::fmt::Display::fmt(e, f),
@ -683,12 +690,11 @@ macro_rules! ele_parse {
)
).into(),
Self::CloseExpected_(span, tok) => vec![
Self::CloseExpected_(qname, span, tok) => vec![
span.note("element starts here"),
tok.span().error(format!(
"expected {}",
// TODO: Actual close name
TtCloseXmlEle::wrap($nt::matcher()),
TtCloseXmlEle::wrap(qname),
)),
],
@ -737,7 +743,7 @@ macro_rules! ele_parse {
XirfToken::Open(qname, span, depth)
) if $nt::matcher().matches(qname) => {
Transition(Attrs_(
(cfg, span.tag_span(), depth),
(cfg, qname, span.tag_span(), depth),
parse_attrs(qname, span)
)).incomplete()
},
@ -747,7 +753,7 @@ macro_rules! ele_parse {
XirfToken::Open(qname, span, depth)
) if cfg.repeat && Self::matcher().matches(qname) => {
Transition(Attrs_(
(cfg, span.tag_span(), depth),
(cfg, qname, span.tag_span(), depth),
parse_attrs(qname, span)
)).incomplete()
},
@ -786,7 +792,7 @@ macro_rules! ele_parse {
Transition(st).incomplete()
}
(Attrs_(meta, sa), tok) => {
(Attrs_(meta @ (_, qname, _, _), sa), tok) => {
sa.delegate_until_obj::<Self, _>(
tok,
EmptyContext,
@ -801,8 +807,10 @@ macro_rules! ele_parse {
)*
} => {
// Optional `OpenSpan` binding
let _ = qname; // avoid unused warning
$(
use crate::xir::parse::attr::AttrParseState;
let $qname_matched = qname;
let $open_span = sa.element_span();
)?
@ -845,21 +853,21 @@ macro_rules! ele_parse {
// XIRF ensures proper nesting,
// so we do not need to check the element name.
(
ExpectClose_((cfg, _, depth))
| CloseRecoverIgnore_((cfg, _, depth), _),
ExpectClose_((cfg, qname, _, depth))
| CloseRecoverIgnore_((cfg, qname, _, depth), _),
XirfToken::Close(_, span, tok_depth)
) if tok_depth == depth => {
$(
let $close_span = span;
)?
$closemap.transition(Closed_(cfg, span.tag_span()))
$closemap.transition(Closed_(cfg, qname, span.tag_span()))
},
(ExpectClose_(meta @ (_, otspan, _)), unexpected_tok) => {
(ExpectClose_(meta @ (_, qname, otspan, _)), unexpected_tok) => {
use crate::parse::Token;
Transition(
CloseRecoverIgnore_(meta, unexpected_tok.span())
).err([<$nt Error_>]::CloseExpected_(otspan, unexpected_tok))
).err([<$nt Error_>]::CloseExpected_(qname, otspan, unexpected_tok))
}
// We're still in recovery,

View File

@ -136,7 +136,7 @@ fn empty_element_no_attrs_with_close_with_spans() {
enum Sut;
type Object = Foo;
Root := QN_PACKAGE(ospan) {
Root := QN_PACKAGE(_, ospan) {
@ {} => Foo::Attr(ospan),
/(cspan) => Foo::Close(cspan),
};
@ -163,7 +163,7 @@ fn empty_element_no_attrs_with_close_with_spans() {
#[test]
fn empty_element_ns_prefix() {
#[derive(Debug, PartialEq, Eq)]
struct Foo;
struct Foo(QName);
impl Object for Foo {}
ele_parse! {
@ -171,8 +171,8 @@ fn empty_element_ns_prefix() {
type Object = Foo;
// This matches `c:*`.
Root := NS_C {
@ {} => Foo,
Root := NS_C(qname, _) {
@ {} => Foo(qname),
};
}
@ -184,9 +184,9 @@ fn empty_element_ns_prefix() {
assert_eq!(
Ok(vec![
Parsed::Incomplete, // [Root] Open
Parsed::Object(Foo), // [Root@] Close (>LA)
Parsed::Incomplete, // [Root] Close (<LA)
Parsed::Incomplete, // [Root] Open
Parsed::Object(Foo(QN_C_EQ)), // [Root@] Close (>LA)
Parsed::Incomplete, // [Root] Close (<LA)
]),
Sut::parse(toks.into_iter()).collect(),
);
@ -230,6 +230,57 @@ fn empty_element_ns_prefix_nomatch() {
);
}
// When a QName matches a namespace prefix,
// that specific QName should be used in subsequent errors,
// such as when expecting a closing tag.
#[test]
fn empty_element_ns_prefix_invalid_close_contains_matching_qname() {
#[derive(Debug, PartialEq, Eq)]
struct Foo;
impl Object for Foo {}
ele_parse! {
enum Sut;
type Object = Foo;
// This matches `c:*`.
Root := NS_C {
@ {} => Foo,
};
}
let unexpected = QN_C_GT;
let span_unexpected = OpenSpan(S2, N);
let toks = vec![
// Just some `c:*`.
XirfToken::Open(QN_C_EQ, OpenSpan(S1, N), Depth(0)),
// We're not expecting a child.
XirfToken::Open(unexpected, span_unexpected, Depth(1)),
];
let mut sut = Sut::parse(toks.into_iter());
// The opening tag parses fine,
// and the unexpected tag successfully terminates attribute parsing.
assert_eq!(sut.next(), Some(Ok(Parsed::Incomplete))); // [Root] Open
assert_eq!(sut.next(), Some(Ok(Parsed::Object(Foo)))); // [Root@] Open (>LA)
// But then consuming the LA will produce an error,
// since we were not expecting a child.
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
// TODO: This references generated identifiers.
ParseError::StateError(SutError_::Root(RootError_::CloseExpected_(
// Verify that the error includes the QName that actually matched.
QN_C_EQ,
OpenSpan(S1, N).tag_span(),
XirfToken::Open(unexpected, span_unexpected, Depth(1)),
))),
err,
);
}
#[test]
fn empty_element_with_attr_bindings() {
#[derive(Debug, PartialEq, Eq)]
@ -480,7 +531,7 @@ fn multiple_child_elements_sequential() {
enum Sut;
type Object = Foo;
Root := QN_PACKAGE(ospan) {
Root := QN_PACKAGE(_, ospan) {
@ {} => Foo::RootOpen(ospan.span()),
/(cspan) => Foo::RootClose(cspan.span()),
@ -492,7 +543,7 @@ fn multiple_child_elements_sequential() {
// Demonstrates that span identifier bindings are scoped to the
// nonterminal block
// (so please keep the identifiers the same as above).
ChildA := QN_CLASSIFY(ospan) {
ChildA := QN_CLASSIFY(_, ospan) {
@ {} => Foo::ChildAOpen(ospan.span()),
/(cspan) => Foo::ChildAClose(cspan.span()),
};
@ -833,6 +884,7 @@ fn child_error_and_recovery_at_close() {
assert_eq!(
// TODO: This references generated identifiers.
ParseError::StateError(SutError_::Root(RootError_::CloseExpected_(
QN_PACKAGE,
OpenSpan(S1, N).tag_span(),
XirfToken::Open(unexpected_a, span_a, Depth(1)),
))),
@ -1366,6 +1418,7 @@ fn child_repetition_invalid_tok_dead() {
// TODO: This references generated identifiers.
Some(Err(ParseError::StateError(SutError_::Root(
RootError_::CloseExpected_(
QN_ROOT,
OpenSpan(S1, N).tag_span(),
XirfToken::Open(unexpected, OpenSpan(S2, N), Depth(1)),
)

View File

@ -142,5 +142,6 @@ pub mod qname {
QN_YIELDS: :L_YIELDS,
QN_C_EQ: L_C:L_EQ,
QN_C_GT: L_C:L_GT,
}
}