diff --git a/tamer/src/sym/prefill.rs b/tamer/src/sym/prefill.rs index fc093413..e760d330 100644 --- a/tamer/src/sym/prefill.rs +++ b/tamer/src/sym/prefill.rs @@ -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", diff --git a/tamer/src/xir/fmt.rs b/tamer/src/xir/fmt.rs index 5ab64474..34983b28 100644 --- a/tamer/src/xir/fmt.rs +++ b/tamer/src/xir/fmt.rs @@ -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; diff --git a/tamer/src/xir/parse/ele.rs b/tamer/src/xir/parse/ele.rs index b55d5a20..55deba98 100644 --- a/tamer/src/xir/parse/ele.rs +++ b/tamer/src/xir/parse/ele.rs @@ -30,7 +30,7 @@ use crate::{ ClosedParseState, Context, ParseState, Token, Transition, TransitionResult, }, - xir::QName, + xir::{Prefix, QName}, }; #[cfg(doc)] @@ -201,15 +201,18 @@ impl StateStack { 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 for NodeMatcher { } } +impl From 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) diff --git a/tamer/src/xir/parse/ele/test.rs b/tamer/src/xir/parse/ele/test.rs index e89ffe5d..474ec0ae 100644 --- a/tamer/src/xir/parse/ele/test.rs +++ b/tamer/src/xir/parse/ele/test.rs @@ -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 ( 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)] diff --git a/tamer/src/xir/st.rs b/tamer/src/xir/st.rs index 2e629f22..bcb82d5a 100644 --- a/tamer/src/xir/st.rs +++ b/tamer/src/xir/st.rs @@ -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, } }