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-13346main
parent
9ad7742ad2
commit
1983e73c81
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -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))))));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue