tamer: xir::flat::XirfToXir: New lowering operation

This parser does exactly what it says it does.  Its implementation is
simple, but I added a test anyway just to prove that it works, and the test
seems more complicated than the implementation itself, given the types
involved.

DEV-13708
main
Mike Gerwitz 2023-02-16 16:54:31 -05:00
parent a5a5a99dbd
commit 79cc61f996
4 changed files with 131 additions and 8 deletions

View File

@ -20,9 +20,9 @@
//!
//! See (parent module)[super] for more information.
use std::fmt::Display;
use crate::{f::Functor, span::Span};
use super::{Expr, Ident, Object, ObjectIndex, ObjectKind, Pkg, Root};
use crate::{f::Functor, span::Span};
use std::fmt::Display;
/// Object types corresponding to variants in [`Object`].
///

View File

@ -51,7 +51,7 @@ use std::{
pub mod prelude {
pub use super::{
ClosedParseState, Context, NoContext, Object, ParseError, ParseState,
ParseStatus, Parsed, Token, Transition, TransitionResult,
ParseStatus, Parsed, ParsedResult, Token, Transition, TransitionResult,
};
}

View File

@ -48,16 +48,14 @@ use super::{
use crate::{
diagnose::{Annotate, AnnotatedSpan, Diagnostic},
f::Functor,
parse::{
ClosedParseState, Context, Object, ParseState, ParsedResult, Token,
Transition, TransitionResult,
},
parse::prelude::*,
span::Span,
sym::{st::is_common_whitespace, GlobalSymbolResolve, SymbolId},
xir::EleSpan,
};
use arrayvec::ArrayVec;
use std::{
convert::Infallible,
error::Error,
fmt::{Debug, Display},
marker::PhantomData,
@ -232,7 +230,7 @@ impl<T: TextType> Functor<Depth> for XirfToken<T> {
/// including the detection of [`Whitespace`].
///
/// See also [`RefinedText`].
pub trait TextType = From<Text> + Token + Eq;
pub trait TextType = From<Text> + Into<Text> + Token + Eq;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Text(pub SymbolId, pub Span);
@ -323,6 +321,15 @@ impl From<Text> for RefinedText {
}
}
impl From<RefinedText> for Text {
fn from(value: RefinedText) -> Self {
match value {
RefinedText::Whitespace(Whitespace(text))
| RefinedText::Unrefined(text) => text,
}
}
}
/// XIRF-compatible attribute parser.
pub trait FlatAttrParseState<const MAX_DEPTH: usize> =
ClosedParseState<Token = XirToken, Object = Attr>
@ -687,5 +694,76 @@ impl From<AttrParseError> for XirToXirfError {
}
}
/// Lower a [`XirfToken`] stream into a [`XirToken`] stream.
///
/// This is the dual of [`XirToXirf`],
/// and is intended to be used when the system _generates_ XML.
/// If you do not need any features of XIRF,
/// and aren't using any operation that produces it,
/// then you may also skip a step and just emit XIR to avoid having to
/// perform this lowering operation.
#[derive(Debug, PartialEq, Eq)]
pub enum XirfToXir<T: TextType> {
Ready(PhantomData<T>),
AttrVal(PhantomData<T>),
}
impl<T: TextType> Default for XirfToXir<T> {
fn default() -> Self {
Self::Ready(Default::default())
}
}
impl<T: TextType> Display for XirfToXir<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "translating XIRF to XIR")
}
}
impl<T: TextType> ParseState for XirfToXir<T> {
type Token = XirfToken<T>;
type Object = XirToken;
type Error = Infallible;
fn parse_token(
self,
tok: Self::Token,
_: NoContext,
) -> TransitionResult<Self::Super> {
use XirToken as Xir;
use XirfToXir::*;
use XirfToken as Xirf;
macro_rules! to {
($tok:expr) => {
Transition(self).ok($tok)
};
}
match tok {
Xirf::Open(qname, ospan, _) => to!(Xir::Open(qname, ospan)),
Xirf::Close(qname, cspan, _) => to!(Xir::Close(qname, cspan)),
Xirf::Attr(attr) => match self {
Self::Ready(p) => Transition(AttrVal(p))
.ok(Xir::AttrName(attr.name(), attr.attr_span().key_span()))
.with_lookahead(Xirf::Attr(attr)),
Self::AttrVal(p) => Transition(Ready(p)).ok(Xir::AttrValue(
attr.value(),
attr.attr_span().value_span(),
)),
},
Xirf::Comment(sym, span, _) => to!(Xir::Comment(sym, span)),
Xirf::Text(x, _) => match x.into() {
Text(sym, span) => to!(Xir::Text(sym, span)),
},
Xirf::CData(sym, span, _) => to!(Xir::CData(sym, span)),
}
}
fn is_accepting(&self, _: &Self::Context) -> bool {
matches!(self, Self::Ready(_))
}
}
#[cfg(test)]
pub mod test;

View File

@ -568,3 +568,48 @@ fn whitespace_refinement() {
}
});
}
// Basic sanity check;
// the implementation is simple enough to verify almost at a glance,
// but the attribute deconstruction with lookahead could be missed so
// it's worth just testing an example.
#[test]
fn xirf_to_xir() {
use crate::parse::Lower;
let xir_toks = vec![
XirToken::Open("a".unwrap_into(), S1.into()),
XirToken::AttrName("attr".unwrap_into(), S2),
XirToken::AttrValue("value".into(), S3),
XirToken::Comment("comment".into(), S4),
XirToken::Text("text".into(), S5),
XirToken::CData("cdata".into(), S6),
XirToken::Close(Some("a".unwrap_into()), S7.into()),
];
// This type incantation
// (a) is a sorry mess because at the time of writing the lowering
// pipeline is still in need of further abstraction; and
// (b) simply parses XIR -> XirToXirf -> XirfToXir -> XIR and asserts
// that the result is the same as what was originally provided.
//
// It really does make sense if you approach it slowly and offer it food.
assert_eq!(
Ok(xir_toks.clone().into_iter().map(Parsed::Object).collect()),
Lower::<XirToXirf<1, Text>, XirfToXir<Text>, _>::lower(
&mut parse::<1, Text>(xir_toks.into_iter()),
|out| out
.filter(|x| !matches!(x, Ok(Parsed::Incomplete)))
.collect::<Result<Vec<_>, _>>()
)
);
// The lowering pipeline above requires compatible errors.
impl From<ParseError<XirfToken<Text>, Infallible>>
for ParseError<XirToken, XirToXirfError>
{
fn from(_value: ParseError<XirfToken<Text>, Infallible>) -> Self {
unreachable!()
}
}
}