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-13708main
parent
a5a5a99dbd
commit
79cc61f996
|
@ -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`].
|
||||
///
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue