tamer: xir::parse::attrstream: Value from SPair

This really does need documentation.

With that said, this changes things up a bit: the value is now derived from
an `SPair` rather than an `Attr`, given that the name is redundant.  We do
not need the attribute name span, since the philosophy is that we're
stripping the document and it should no longer be important beyond the
current context.

It does call into question errors, but my intent in the future is to be able
to have the lowering pipline augment errors with its current state---since
we're streaming, then an error that is encountered during lowering of an
element will still have the element parser in the state representing the
parsing of that element; so that information does not need to be propagated
down the pipeline, but can be augmented as it bubbles back up.

More on that at some point in the future; not right now.

DEV-13346
main
Mike Gerwitz 2022-11-29 16:48:15 -05:00
parent 9ad7742ad2
commit 1983e73c81
2 changed files with 72 additions and 97 deletions

View File

@ -22,7 +22,8 @@
//! The parser generator is invoked via the macro
//! [`attr_parse!`](crate::attr_parse),
//! which expects a `match`-like definition describing the mapping between
//! attribute [QNames](crate::xir::QName) and the final value.
//! attribute [QNames](crate::xir::QName) and a value derived from the
//! attribute value.
//! It produces a streaming attribute parser.
//!
//! All fields recognized by this parser are implicitly optional,
@ -46,28 +47,20 @@ macro_rules! attr_parse_stream {
type Object = $objty:ty;
type ValueError = $evty:ty;
$(vis($vis:vis);)?
$(#[$st_attr:meta])? $state_name:ident {
$(#[$st_attr:meta])?
$vis:vis $state_name:ident {
$(
$(#[$fattr:meta])*
$qname:ident => $ty:ty,
$qname:ident => $attrf:expr,
)*
}
) => { paste::paste! {
$(
// This provides a nice error on $ty itself at the call site,
// rather than relying on `Into::into` to cause the error
// later on,
// which places the error inside the macro definition.
assert_impl_all!($ty: TryFrom<crate::xir::attr::Attr>);
)*
$(#[$st_attr])?
///
#[doc=concat!("Parser producing [`", stringify!($struct_name), "`].")]
// TODO: This can be extracted out of the macro.
#[derive(Debug, PartialEq, Eq)]
$($vis)? enum $state_name {
$vis enum $state_name {
Parsing(crate::xir::QName, crate::xir::OpenSpan),
Done(crate::xir::QName, crate::xir::OpenSpan),
}
@ -77,7 +70,7 @@ macro_rules! attr_parse_stream {
/// TODO: Remove once integrated with `ele_parse!`.
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq, Eq, Default)]
$($vis)? struct [<$state_name Fields>];
$vis struct [<$state_name Fields>];
impl crate::xir::parse::AttrParseState for $state_name {
type ValueError = $evty;
@ -167,6 +160,7 @@ macro_rules! attr_parse_stream {
};
#[allow(unused_imports)]
use crate::xir::attr::{Attr, AttrSpan}; // unused if no attrs
use crate::parse::util::SPair;
let ele_name = self.element_name();
@ -177,9 +171,9 @@ macro_rules! attr_parse_stream {
// We don't use `$qname:pat` because we reuse
// `$qname` for error messages.
(st @ Self::Parsing(_, _), flat::XirfToken::Attr(
attr @ Attr(qn, _, AttrSpan(_kspan, _))
Attr(qn, v, AttrSpan(_, vspan))
)) if qn == $qname => {
match attr.try_into() {
match Into::<Result<$objty, $evty>>::into($attrf(SPair(v, vspan))) {
Ok(value) => {
Transition(st).ok::<$objty>(value)
},

View File

@ -20,9 +20,8 @@
use super::super::{AttrParseError, AttrParseState};
use crate::{
diagnose::{AnnotatedSpan, Diagnostic},
parse::{self, ParseError, Parsed, Parser, TokenStream},
span::{dummy::*, Span},
sym::SymbolId,
parse::{self, util::SPair, ParseError, Parsed, Parser, TokenStream},
span::dummy::*,
xir::{
attr::{Attr, AttrSpan},
flat::XirfToken,
@ -54,39 +53,58 @@ where
Parser::with_state(S::with_element(QN_ELE, SE), toks)
}
#[derive(Debug, PartialEq, Eq)]
enum Foo {
A(SPair),
B(SPair),
Unused(SPair),
}
impl parse::Object for Foo {}
impl<E> From<Foo> for Result<Foo, E> {
fn from(value: Foo) -> Self {
Ok(value)
}
}
// Remember: we only describe what is _permissable_,
// not what is required or what order it must appear in.
// That is the responsibility of parsers lower in the pipeline.
#[test]
fn attrs_any_order_and_optional() {
attr_parse_stream! {
type Object = Attr;
type Object = Foo;
type ValueError = Infallible;
ValuesState {
QN_NAME => Attr,
QN_YIELDS => Attr,
QN_NAME => Foo::A,
// The above is the same as this longer form:
QN_YIELDS => |spair| Foo::B(spair),
// No value will be provided for this one,
// which is okay since all are implicitly optional.
QN_INDEX => Attr,
QN_INDEX => Foo::Unused,
}
}
let attr_name = Attr(QN_NAME, "val_name".into(), AttrSpan(S1, S2));
let attr_yields = Attr(QN_YIELDS, "val_value".into(), AttrSpan(S2, S3));
let name = "val_name".into();
let yields = "val_value".into();
let attr_name = Attr(QN_NAME, name, AttrSpan(S1, S2));
let attr_yields = Attr(QN_YIELDS, yields, AttrSpan(S2, S3));
// @yields then @name just to emphasize that order does not matter.
let toks = vec![
XirfToken::Attr(attr_yields.clone()),
XirfToken::Attr(attr_name.clone()),
];
let toks = vec![XirfToken::Attr(attr_yields), XirfToken::Attr(attr_name)];
assert_eq!(
// Simply parses back out the attributes;
// see following tests further value parsing.
// Note that we omit one of the attributes declared above.
Ok(vec![Object(attr_yields), Object(attr_name),]),
Ok(vec![
Object(Foo::B(SPair(yields, S3))),
Object(Foo::A(SPair(name, S2)))
]),
sut_parse::<ValuesState, _>(toks.into_iter()).collect(),
);
}
@ -96,12 +114,12 @@ fn attrs_any_order_and_optional() {
#[test]
fn attrs_empty() {
attr_parse_stream! {
type Object = Attr;
type Object = Foo;
type ValueError = Infallible;
ValuesState {
// We will not provide a value for this.
QN_NAME => Attr,
QN_NAME => Foo::Unused,
}
}
@ -113,66 +131,18 @@ fn attrs_empty() {
);
}
#[test]
fn attr_value_into() {
// Yes, this is like SPair,
// but the point of this test is to be useful in isolation,
// so please do not couple this with SPair.
#[derive(Debug, PartialEq, Eq)]
struct Foo(SymbolId, Span);
impl From<Attr> for Foo {
fn from(attr: Attr) -> Self {
Foo(attr.value(), attr.attr_span().value_span())
}
}
impl parse::Object for Foo {}
attr_parse_stream! {
type Object = Foo;
type ValueError = Infallible;
ValueIntoState {
QN_NAME => Foo,
QN_YIELDS => Foo,
}
}
let val_name = "val_name".into();
let val_yields = "val_yields".into();
let attr_name = Attr(QN_NAME, val_name, AttrSpan(S1, S2));
let attr_yields = Attr(QN_YIELDS, val_yields, AttrSpan(S2, S3));
let toks = vec![
XirfToken::Attr(attr_name.clone()),
XirfToken::Attr(attr_yields.clone()),
];
assert_eq!(
Ok(vec![Object(Foo(val_name, S2)), Object(Foo(val_yields, S3))]),
sut_parse::<ValueIntoState, _>(toks.into_iter()).collect(),
);
}
// This test would fail at compile time.
#[test]
fn attr_value_error() {
#[derive(Debug, PartialEq, Eq)]
struct Foo;
impl TryFrom<Attr> for Foo {
impl TryFrom<SPair> for Foo {
type Error = FooError;
fn try_from(attr: Attr) -> Result<Self, Self::Error> {
Err(FooError(attr.value()))
fn try_from(spair: SPair) -> Result<Self, Self::Error> {
Err(FooError(spair))
}
}
impl parse::Object for Foo {}
#[derive(Debug, PartialEq)]
struct FooError(SymbolId);
struct FooError(SPair);
impl Error for FooError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
@ -192,13 +162,22 @@ fn attr_value_error() {
}
}
impl Into<Result<Foo, FooError>> for FooError {
fn into(self) -> Result<Foo, FooError> {
Err(self)
}
}
attr_parse_stream! {
type Object = Foo;
type ValueError = FooError;
ValueTryIntoState {
QN_NAME => Foo,
QN_YIELDS => Foo,
// Explicit form:
QN_NAME => |s| Err(FooError(s)),
// Conversion using `Into`:
QN_YIELDS => FooError,
}
}
@ -216,7 +195,7 @@ fn attr_value_error() {
assert_eq!(
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
FooError(val_name),
FooError(SPair(val_name, S2)),
QN_ELE
)))),
sut.next(),
@ -225,7 +204,7 @@ fn attr_value_error() {
// TryInto on `Option` inner type.
assert_eq!(
Some(Err(ParseError::StateError(AttrParseError::InvalidValue(
FooError(val_yields),
FooError(SPair(val_yields, S3)),
QN_ELE
)))),
sut.next(),
@ -235,27 +214,29 @@ fn attr_value_error() {
#[test]
fn unexpected_attr_with_recovery() {
attr_parse_stream! {
type Object = Attr;
type Object = Foo;
type ValueError = Infallible;
UnexpectedState {
QN_NAME => Attr,
QN_SRC => Attr,
QN_NAME => Foo::A,
QN_SRC => Foo::B,
}
}
let attr_name = Attr(QN_NAME, "val_name".into(), AttrSpan(S1, S2));
let name = "val_name".into();
let src = "val_src".into();
let attr_name = Attr(QN_NAME, name, AttrSpan(S1, S2));
let attr_unexpected = Attr(QN_TYPE, "unexpected".into(), AttrSpan(S1, S2));
let attr_src = Attr(QN_SRC, "val_src".into(), AttrSpan(S2, S3));
let attr_src = Attr(QN_SRC, src, AttrSpan(S2, S3));
let toks = vec![
// This is expected:
XirfToken::Attr(attr_name.clone()),
XirfToken::Attr(attr_name),
// NOT expected (produce an error):
XirfToken::Attr(attr_unexpected.clone()),
// <Recovery must take place here.>
// This is expected after recovery:
XirfToken::Attr(attr_src.clone()),
XirfToken::Attr(attr_src),
];
let mut sut = Parser::with_state(
@ -263,7 +244,7 @@ fn unexpected_attr_with_recovery() {
toks.into_iter(),
);
assert_eq!(sut.next(), Some(Ok(Object(attr_name))));
assert_eq!(sut.next(), Some(Ok(Object(Foo::A(SPair(name, S2))))));
// This will fail at the unknown attribute,
// and must then remain in a state where parsing can be resumed.
@ -279,5 +260,5 @@ fn unexpected_attr_with_recovery() {
)))),
);
assert_eq!(sut.next(), Some(Ok(Object(attr_src))));
assert_eq!(sut.next(), Some(Ok(Object(Foo::B(SPair(src, S3))))));
}