tamer: xir::parse::ele: Initial namespace prefix matching support

This allows matching on a namespace prefix by providing a `Prefix` instead
of a `QName`.  This works, but is missing a couple notable things (and
possibly more):

  1. Tracking the QName that is _actually_ matched so that it can be used in
     messages stating what the expected closing tag is; and
  2. Making that QName available via a binding.

This will be used to match on `t:*` in NIR.  If you're wondering how
attribute parsing is supposed to work with that (of course you're wondering
that, random person reading this)---that'll have to work differently for
those matches, since template shorthand application contains argument names
as attributes.

DEV-7145
main
Mike Gerwitz 2022-08-10 16:50:02 -04:00
parent f9fe4aa13b
commit 8cb03d8d16
5 changed files with 116 additions and 19 deletions

View File

@ -509,6 +509,7 @@ pub mod st {
L_DTYPE: cid "dtype",
L_ELIG_CLASS_YIELDS: tid "elig-class-yields",
L_EMPTY: cid "empty",
L_EQ: cid "eq",
L_EXEC: cid "exec",
L_EXPORT: cid "export",
L_EXTERN: cid "extern",

View File

@ -19,7 +19,9 @@
//! XIR formatting types for use with [`crate::fmt`]
use crate::fmt::{AndQualConjList, Delim, OrQualConjList, Prefix, Raw, Tt};
use crate::fmt::{
AndQualConjList, Delim, OrQualConjList, Prefix, Raw, Suffix, Tt,
};
/// Denote an XML attribute by prefixing the value with `@`.
pub type XmlAttr = Prefix<"@", Raw>;
@ -33,6 +35,12 @@ pub type OpenXmlEle = Delim<"<", ">", Raw>;
/// Opening tag for XML element.
pub type CloseXmlEle = Delim<"</", ">", Raw>;
/// "`ns:*`" given a namespace prefix `ns`.
///
/// TODO: It'd be nice to be able to have Raw require a specific type to
/// ensure that we're given a prefix.
pub type XmlPrefixAnyLocal = Suffix<":*", Raw>;
/// Opening tag for XML element as teletypewriter
/// (for use in sentences).
pub type TtOpenXmlEle = Tt<OpenXmlEle>;

View File

@ -30,7 +30,7 @@ use crate::{
ClosedParseState, Context, ParseState, Token, Transition,
TransitionResult,
},
xir::QName,
xir::{Prefix, QName},
};
#[cfg(doc)]
@ -201,15 +201,18 @@ impl<S: ClosedParseState> StateStack<S> {
pub enum NodeMatcher {
/// Static [`QName`] with a simple equality check.
QName(QName),
/// Any element with a matching [`Prefix`].
Prefix(Prefix),
}
impl NodeMatcher {
/// Match against the provided [`QName`].
pub fn matches(&self, qname: QName) -> bool {
matches!(
self,
Self::QName(qn_match) if qn_match == &qname
)
match self {
Self::QName(qn_match) if qn_match == &qname => true,
Self::Prefix(prefix) if Some(*prefix) == qname.prefix() => true,
_ => false,
}
}
}
@ -219,10 +222,19 @@ impl From<QName> for NodeMatcher {
}
}
impl From<Prefix> for NodeMatcher {
fn from(prefix: Prefix) -> Self {
Self::Prefix(prefix)
}
}
impl Display for NodeMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use crate::xir::fmt::XmlPrefixAnyLocal;
match self {
Self::QName(qname) => Display::fmt(qname, f),
Self::Prefix(prefix) => XmlPrefixAnyLocal::fmt(prefix, f),
}
}
}
@ -516,8 +528,7 @@ macro_rules! ele_parse {
impl crate::xir::parse::EleParseState for $nt {}
impl $nt {
/// [`QName`](crate::xir::QName) of the element recognized
/// by this parser.
/// Matcher describing the node recognized by this parser.
#[allow(dead_code)] // used by sum parser
fn matcher() -> crate::xir::parse::NodeMatcher {
crate::xir::parse::NodeMatcher::from($qname)
@ -548,7 +559,7 @@ macro_rules! ele_parse {
Self::Expecting_(_) => write!(
f,
"expecting opening tag {}",
TtOpenXmlEle::wrap($qname),
TtOpenXmlEle::wrap(Self::matcher()),
),
Self::RecoverEleIgnore_(_, name, _, _)
| Self::RecoverEleIgnoreClosed_(_, name, _) => write!(
@ -557,26 +568,28 @@ macro_rules! ele_parse {
with unexpected name {given} \
(expected {expected})",
given = TtQuote::wrap(name),
expected = TtQuote::wrap($qname),
expected = TtQuote::wrap(Self::matcher()),
),
Self::CloseRecoverIgnore_((_, _, depth), _) => write!(
f,
"attempting to recover by ignoring input \
until the expected end tag {expected} \
at depth {depth}",
expected = TtCloseXmlEle::wrap($qname),
expected = TtCloseXmlEle::wrap(Self::matcher()),
),
Self::Attrs_(_, sa) => std::fmt::Display::fmt(sa, f),
Self::ExpectClose_((_, _, depth)) => write!(
f,
"expecting closing element {} at depth {depth}",
TtCloseXmlEle::wrap($qname)
// TODO: Actual closing tag name
// (this may be a prefix).
TtCloseXmlEle::wrap(Self::matcher())
),
Self::Closed_(_, _cfg) => write!(
f,
"done parsing element {}",
TtQuote::wrap($qname)
TtQuote::wrap(Self::matcher())
),
$(
// TODO: A better description.
@ -639,12 +652,13 @@ macro_rules! ele_parse {
f,
"unexpected {unexpected} (expecting {expected})",
unexpected = TtOpenXmlEle::wrap(name),
expected = TtOpenXmlEle::wrap($qname),
expected = TtOpenXmlEle::wrap($nt::matcher()),
),
Self::CloseExpected_(_, tok) => write!(
f,
"expected {}, but found {}",
TtCloseXmlEle::wrap($qname),
// TODO: Actual close name.
TtCloseXmlEle::wrap($nt::matcher()),
TtQuote::wrap(tok)
),
Self::Attrs_(e) => std::fmt::Display::fmt(e, f),
@ -665,7 +679,7 @@ macro_rules! ele_parse {
Self::UnexpectedEle_(_, ospan) => ospan.error(
format!(
"expected {ele_name} here",
ele_name = TtQuote::wrap($qname)
ele_name = TtQuote::wrap($nt::matcher())
)
).into(),
@ -673,7 +687,8 @@ macro_rules! ele_parse {
span.note("element starts here"),
tok.span().error(format!(
"expected {}",
TtCloseXmlEle::wrap($qname),
// TODO: Actual close name
TtCloseXmlEle::wrap($nt::matcher()),
)),
],
@ -730,7 +745,7 @@ macro_rules! ele_parse {
(
Closed_(cfg, ..),
XirfToken::Open(qname, span, depth)
) if cfg.repeat && qname == $qname => {
) if cfg.repeat && Self::matcher().matches(qname) => {
Transition(Attrs_(
(cfg, span.tag_span(), depth),
parse_attrs(qname, span)

View File

@ -43,7 +43,7 @@ use crate::{
xir::{
attr::{Attr, AttrSpan},
flat::{Depth, RefinedText, Text, Whitespace, XirfToken},
st::qname::*,
st::{prefix::*, qname::*},
CloseSpan, EleNameLen, EleSpan, OpenSpan, QName,
},
};
@ -159,6 +159,77 @@ fn empty_element_no_attrs_with_close_with_spans() {
);
}
// Match on a namespace prefix rather than a static QName.
#[test]
fn empty_element_ns_prefix() {
#[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 toks = vec![
// Just some `c:*`.
XirfToken::Open(QN_C_EQ, OpenSpan(S1, N), Depth(0)),
XirfToken::Close(None, CloseSpan::empty(S2), Depth(0)),
];
assert_eq!(
Ok(vec![
Parsed::Incomplete, // [Root] Open
Parsed::Object(Foo), // [Root@] Close (>LA)
Parsed::Incomplete, // [Root] Close (<LA)
]),
Sut::parse(toks.into_iter()).collect(),
);
}
#[test]
fn empty_element_ns_prefix_nomatch() {
#[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 span = OpenSpan(S1, N);
// Non `c:*` element.
let unexpected = QN_PACKAGE;
let toks = vec![
XirfToken::Open(unexpected, span, Depth(0)),
XirfToken::Close(None, CloseSpan::empty(S2), Depth(0)),
];
let mut sut = Sut::parse(toks.into_iter());
let err = sut.next().unwrap().unwrap_err();
assert_eq!(
// TODO: This references generated identifiers.
ParseError::StateError(SutError_::Root(RootError_::UnexpectedEle_(
unexpected,
span.name_span()
))),
err,
);
}
#[test]
fn empty_element_with_attr_bindings() {
#[derive(Debug, PartialEq, Eq)]

View File

@ -140,5 +140,7 @@ pub mod qname {
QN_XMLNS_L: L_XMLNS:L_L,
QN_XMLNS_PREPROC: L_XMLNS:L_PREPROC,
QN_YIELDS: :L_YIELDS,
QN_C_EQ: L_C:L_EQ,
}
}