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-7145main
parent
8cb03d8d16
commit
67ee914505
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue