tamer: xir::parse::ele: Introduce open/close span bindings

This adds the ability to bind identifiers to represent `OpenSpan` and
`CloseSpan`, available to the `@` and `/` maps.  Since identifiers in TAME
originate from attributes, this may not get a whole lot of use, but it's
important to be available.

There is some awkwardness in that the opening span appears to be scoped to
the entire nonterminal, but it's actually only available in the `@`
mapping.  I'll change this if it's actually needed; this keeps things simple
for now.

DEV-7145
main
Mike Gerwitz 2022-07-13 23:42:51 -04:00
parent cceb8c7fb9
commit 1fdfc0aa4d
4 changed files with 113 additions and 48 deletions

View File

@ -272,7 +272,10 @@ pub trait ParseState: PartialEq + Eq + Display + Debug + Sized {
mut context: C,
into: impl FnOnce(Self) -> Transition<SP>,
_dead: impl FnOnce() -> Transition<SP>,
objf: impl FnOnce(<Self as ParseState>::Object) -> TransitionResult<SP>,
objf: impl FnOnce(
Self,
<Self as ParseState>::Object,
) -> TransitionResult<SP>,
) -> TransitionResult<SP>
where
Self: PartiallyStitchableParseState<SP>,
@ -290,7 +293,8 @@ pub trait ParseState: PartialEq + Eq + Display + Debug + Sized {
}
TransitionData::Result(Ok(Obj(obj)), lookahead) => {
objf(obj).maybe_with_lookahead(lookahead)
// TODO: check accepting
objf(newst, obj).maybe_with_lookahead(lookahead)
}
TransitionData::Result(result, lookahead) => TransitionResult(

View File

@ -41,8 +41,7 @@ use crate::{
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
fmt::ListDisplayWrapper,
parse::ParseState,
span::Span,
xir::{attr::Attr, fmt::XmlAttrList, QName},
xir::{attr::Attr, fmt::XmlAttrList, EleSpan, OpenSpan, QName},
};
use std::{error::Error, fmt::Display};
@ -107,6 +106,7 @@ impl<S: AttrParseState> Diagnostic for AttrParseError<S> {
match self {
Self::MissingRequired(st) => st
.element_span()
.tag_span()
.error(format!(
"missing required {}",
XmlAttrList::wrap(&st.required_missing()),
@ -129,13 +129,13 @@ pub trait AttrParseState: ParseState {
/// Begin attribute parsing within the context of the provided element.
///
/// This is used to provide diagnostic information.
fn with_element(ele: QName, span: Span) -> Self;
fn with_element(ele: QName, span: OpenSpan) -> Self;
/// Name of the element being parsed.
fn element_name(&self) -> QName;
/// Span associated with the element being parsed.
fn element_span(&self) -> Span;
fn element_span(&self) -> OpenSpan;
/// Attempt to narrow into the final type by checking the availability
/// of required attribute values.
@ -161,7 +161,7 @@ pub trait AttrParseState: ParseState {
/// inferred,
/// so that the expression reads more like natural language.
#[cfg(test)] // currently only used by tests; remove when ready
pub fn parse_attrs<S: AttrParseState>(ele: QName, span: Span) -> S {
pub fn parse_attrs<S: AttrParseState>(ele: QName, span: OpenSpan) -> S {
S::with_element(ele, span)
}
@ -196,7 +196,7 @@ macro_rules! attr_parse {
#[derive(Debug, PartialEq, Eq)]
$vis struct $state_name {
#[doc(hidden)]
___ctx: (crate::xir::QName, Span),
___ctx: (crate::xir::QName, crate::xir::OpenSpan),
#[doc(hidden)]
___done: bool,
$(
@ -205,7 +205,10 @@ macro_rules! attr_parse {
}
impl crate::xir::parse::AttrParseState for $state_name {
fn with_element(ele: crate::xir::QName, span: Span) -> Self {
fn with_element(
ele: crate::xir::QName,
span: crate::xir::OpenSpan
) -> Self {
Self {
___ctx: (ele, span),
___done: false,
@ -221,7 +224,7 @@ macro_rules! attr_parse {
}
}
fn element_span(&self) -> Span {
fn element_span(&self) -> crate::xir::OpenSpan {
match self.___ctx {
(_, span) => span,
}
@ -270,7 +273,7 @@ macro_rules! attr_parse {
}
impl $state_name {
fn done_with_element(ele: crate::xir::QName, span: Span) -> Self {
fn done_with_element(ele: crate::xir::QName, span: OpenSpan) -> Self {
use crate::xir::parse::attr::AttrParseState;
let mut new = Self::with_element(ele, span);
@ -421,7 +424,7 @@ mod test {
const S1: Span = DUMMY_SPAN;
const S2: Span = S1.offset_add(1).unwrap();
const S3: Span = S2.offset_add(1).unwrap();
const SE: Span = S1.offset_add(100).unwrap();
const SE: OpenSpan = OpenSpan(S1.offset_add(100).unwrap(), 0);
// Random choice of QName for tests.
const QN_ELE: QName = QN_YIELDS;
@ -702,7 +705,7 @@ mod test {
// Manually construct the partial state rather than parsing tokens.
// `required_missing_values` above verifies that this state is what
// is in fact constructed from a failed parsing attempt.
let mut partial = ReqMissingState::with_element(QN_ELE, S1);
let mut partial = ReqMissingState::with_element(QN_ELE, SE);
partial.name.replace(ATTR_NAME);
partial.yields.replace(ATTR_YIELDS);
@ -735,7 +738,7 @@ mod test {
/// See also [`error_contains_all_required_missing_attr_names`].
#[test]
fn diagnostic_message_contains_all_required_missing_attr_name() {
let mut partial = ReqMissingState::with_element(QN_ELE, S1);
let mut partial = ReqMissingState::with_element(QN_ELE, SE);
partial.name.replace(ATTR_NAME);
partial.yields.replace(ATTR_YIELDS);
@ -743,7 +746,7 @@ mod test {
let desc = err.describe();
// The diagnostic message should reference the element.
assert_eq!(desc[0].span(), S1);
assert_eq!(desc[0].span(), SE.span());
// It should re-state the required attributes,
// since this is where the user will most likely be looking.

View File

@ -29,8 +29,11 @@ macro_rules! ele_parse {
ele_parse!(@!nonterm_def <$objty> $nt $($rest)*);
};
(@!nonterm_def <$objty:ty> $nt:ident $qname:ident { $($matches:tt)* } $($rest:tt)*) => {
ele_parse!(@!ele_expand_body <$objty> $nt $qname $($matches)*);
(@!nonterm_def <$objty:ty>
$nt:ident $qname:ident $(($($ntp:tt)*))?
{ $($matches:tt)* } $($rest:tt)*
) => {
ele_parse!(@!ele_expand_body <$objty> $nt $qname ($($($ntp)*)?) $($matches)*);
ele_parse! {
type Object = $objty;
@ -50,9 +53,9 @@ macro_rules! ele_parse {
// Expand the provided data to a more verbose form that provides the
// context necessary for state transitions.
(@!ele_expand_body <$objty:ty> $nt:ident $qname:ident
(@!ele_expand_body <$objty:ty> $nt:ident $qname:ident ($($ntp:tt)*)
@ { $($attrbody:tt)* } => $attrmap:expr,
$(/ => $closemap:expr,)?
$(/$(($close_span:ident))? => $closemap:expr,)?
// Nonterminal references are provided as a list.
$(
@ -60,9 +63,9 @@ macro_rules! ele_parse {
)*
) => {
ele_parse! {
@!ele_dfn_body <$objty> $nt $qname
@!ele_dfn_body <$objty> $nt $qname ($($ntp)*)
@ { $($attrbody)* } => $attrmap,
/ => ele_parse!(@!ele_close $($closemap)?),
/$($($close_span)?)? => ele_parse!(@!ele_close $($closemap)?),
<> {
$(
@ -91,7 +94,7 @@ macro_rules! ele_parse {
crate::parse::ParseStatus::Object($close)
};
(@!ele_dfn_body <$objty:ty> $nt:ident $qname:ident
(@!ele_dfn_body <$objty:ty> $nt:ident $qname:ident ($($open_span:ident)?)
// Attribute definition special form.
@ {
// We must lightly parse attributes here so that we can retrieve
@ -105,7 +108,7 @@ macro_rules! ele_parse {
// Close expression
// (defaulting to Incomplete via @!ele_expand_body).
/ => $closemap:expr,
/$($close_span:ident)? => $closemap:expr,
// Nonterminal references.
<> {
@ -277,7 +280,7 @@ macro_rules! ele_parse {
match (self, tok) {
(Expecting_, XirfToken::Open(qname, span, ..)) if qname == $qname => {
Transition(Attrs_(parse_attrs(qname, span.tag_span())))
Transition(Attrs_(parse_attrs(qname, span)))
.incomplete()
},
@ -304,13 +307,22 @@ macro_rules! ele_parse {
EmptyContext,
|sa| Transition(Attrs_(sa)),
|| unreachable!("see ParseState::delegate_until_obj dead"),
|attrs| {
|#[allow(unused_variables)] sa, attrs| {
let obj = match attrs {
// Attribute field bindings for `$attrmap`
[<$nt Attrs_>] {
$(
$field,
)*
} => $attrmap,
} => {
// Optional `OpenSpan` binding
$(
use crate::xir::parse::attr::AttrParseState;
let $open_span = sa.element_span();
)?
$attrmap
},
};
Transition($ntfirst(Default::default())).ok(obj)
@ -331,8 +343,12 @@ macro_rules! ele_parse {
// XIRF ensures proper nesting,
// so this must be our own closing tag.
(ExpectClose_(_), XirfToken::Close(_, span, _)) =>
$closemap.transition(Closed_(span.tag_span())),
(ExpectClose_(_), XirfToken::Close(_, span, _)) => {
$(
let $close_span = span;
)?
$closemap.transition(Closed_(span.tag_span()))
},
// TODO: Use `is_accepting` guard if we do not utilize
// exhaustiveness check.

View File

@ -115,6 +115,44 @@ fn empty_element_no_attrs_with_close() {
);
}
// Same as above,
// but also with opening and closing spans.
#[test]
fn empty_element_no_attrs_with_close_with_spans() {
#[derive(Debug, PartialEq, Eq)]
enum Foo {
Attr(OpenSpan),
Close(CloseSpan),
}
impl crate::parse::Object for Foo {}
ele_parse! {
type Object = Foo;
Sut := QN_PACKAGE(ospan) {
@ {} => Foo::Attr(ospan),
/(cspan) => Foo::Close(cspan),
}
}
let toks = vec![
// Length (second argument) here is arbitrary.
XirfToken::Open(QN_PACKAGE, OpenSpan(S1, N), Depth(0)),
XirfToken::Close(None, CloseSpan::empty(S2), Depth(0)),
];
use Parsed::*;
assert_eq!(
Ok(vec![
Incomplete, // [Sut] Open
Object(Foo::Attr(OpenSpan(S1, N))), // [Sut@] Close (>LA)
Object(Foo::Close(CloseSpan::empty(S2))), // [Sut] Close (<LA)
]),
Sut::parse(toks.into_iter()).collect(),
);
}
#[test]
fn empty_element_with_attr_bindings() {
#[derive(Debug, PartialEq, Eq)]
@ -304,31 +342,34 @@ fn single_child_element() {
fn multiple_child_elements_sequential() {
#[derive(Debug, PartialEq, Eq)]
enum Foo {
RootOpen,
ChildAOpen,
ChildAClose,
RootOpen(Span),
ChildAOpen(Span),
ChildAClose(Span),
ChildBOpen,
ChildBClose,
RootClose,
RootClose(Span),
}
impl Object for Foo {}
impl crate::parse::Object for Foo {}
ele_parse! {
type Object = Foo;
Sut := QN_PACKAGE {
@ {} => Foo::RootOpen,
/ => Foo::RootClose,
Sut := QN_PACKAGE(ospan) {
@ {} => Foo::RootOpen(ospan.span()),
/(cspan) => Foo::RootClose(cspan.span()),
// Order matters here.
ChildA,
ChildB,
}
ChildA := QN_CLASSIFY {
@ {} => Foo::ChildAOpen,
/ => Foo::ChildAClose,
// Demonstrates that span identifier bindings are scoped to the
// nonterminal block
// (so please keep the identifiers the same as above).
ChildA := QN_CLASSIFY(ospan) {
@ {} => Foo::ChildAOpen(ospan.span()),
/(cspan) => Foo::ChildAClose(cspan.span()),
}
ChildB := QN_EXPORT {
@ -348,17 +389,18 @@ fn multiple_child_elements_sequential() {
XirfToken::Close(Some(QN_PACKAGE), CloseSpan(S5, N), Depth(0)),
];
use Parsed::*;
assert_eq!(
Ok(vec![
Parsed::Incomplete, // [Sut] Root Open
Parsed::Object(Foo::RootOpen), // [Sut@] ChildA Open (>LA)
Parsed::Incomplete, // [ChildA] ChildA Open (<LA)
Parsed::Object(Foo::ChildAOpen), // [ChildA@] ChildA Close (>LA)
Parsed::Object(Foo::ChildAClose), // [ChildA] ChildA Close (<LA)
Parsed::Incomplete, // [ChildB] ChildB Open
Parsed::Object(Foo::ChildBOpen), // [ChildB@] ChildB Close (>LA)
Parsed::Object(Foo::ChildBClose), // [ChildB] ChildB Close (<LA)
Parsed::Object(Foo::RootClose), // [Sut] Root Close
Incomplete, // [Sut] Root Open
Object(Foo::RootOpen(S1)), // [Sut@] ChildA Open (>LA)
Incomplete, // [ChildA] ChildA Open (<LA)
Object(Foo::ChildAOpen(S2)), // [ChildA@] ChildA Close (>LA)
Object(Foo::ChildAClose(S3)), // [ChildA] ChildA Close (<LA)
Incomplete, // [ChildB] ChildB Open
Object(Foo::ChildBOpen), // [ChildB@] ChildB Close (>LA)
Object(Foo::ChildBClose), // [ChildB] ChildB Close (<LA)
Object(Foo::RootClose(S5)), // [Sut] Root Close
]),
Sut::parse(toks.into_iter()).collect(),
);