forked from lavina/lavina
162 lines
5.4 KiB
Rust
162 lines
5.4 KiB
Rust
use std::ops::Coroutine;
|
|
use std::pin::Pin;
|
|
|
|
use quick_xml::events::Event;
|
|
use quick_xml::name::ResolveResult;
|
|
use quick_xml::NsReader;
|
|
|
|
use anyhow::Result;
|
|
|
|
mod ignore;
|
|
pub use ignore::Ignore;
|
|
|
|
/// Types which can be parsed from an XML input stream.
|
|
///
|
|
/// Example:
|
|
/// ```
|
|
/// #![feature(type_alias_impl_trait)]
|
|
/// #![feature(impl_trait_in_assoc_type)]
|
|
/// #![feature(coroutines)]
|
|
/// # use proto_xmpp::xml::FromXml;
|
|
/// # use quick_xml::events::Event;
|
|
/// # use quick_xml::name::ResolveResult;
|
|
/// # use proto_xmpp::xml::Parser;
|
|
/// # use anyhow::Result;
|
|
///
|
|
/// struct MyStruct;
|
|
/// impl FromXml for MyStruct {
|
|
/// type P = impl Parser<Output = Result<Self>>;
|
|
///
|
|
/// fn parse() -> Self::P {
|
|
/// |(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
|
|
/// (namespace, event) = yield;
|
|
/// Ok(MyStruct)
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
pub trait FromXml: Sized {
|
|
/// The type of parser instances.
|
|
///
|
|
/// If the result type of the [parse] is anonymous, this type member can be defined by using `impl Trait`.
|
|
type P: Parser<Output = Result<Self>>;
|
|
|
|
/// Creates a new instance of a parser with an initial state.
|
|
fn parse() -> Self::P;
|
|
}
|
|
|
|
pub trait ToXml: Sized {
|
|
fn serialize(&self, events: &mut Vec<Event<'static>>);
|
|
}
|
|
|
|
pub trait FromXmlTag: FromXml {
|
|
const NAME: &'static str;
|
|
const NS: &'static str;
|
|
}
|
|
|
|
/// A stateful parser instance which consumes XML events until the parsing is complete.
|
|
///
|
|
/// Usually implemented with the experimental coroutine syntax, which yields to consume the next XML event,
|
|
/// and returns the final result when the parsing is done.
|
|
pub trait Parser: Sized {
|
|
type Output;
|
|
|
|
/// Advance the parsing by one XML event.
|
|
///
|
|
/// This method consumes `self`, but if the parsing is incomplete,
|
|
/// it will return the next state of the parser in the returned result.
|
|
/// Otherwise, it will return the final result of parsing.
|
|
fn consume<'a>(self: Self, namespace: ResolveResult, event: &Event<'a>) -> Continuation<Self, Self::Output>;
|
|
}
|
|
|
|
impl<T, Out> Parser for T
|
|
where
|
|
T: Coroutine<(ResolveResult<'static>, &'static Event<'static>), Yield = (), Return = Out> + Unpin,
|
|
{
|
|
type Output = Out;
|
|
|
|
fn consume<'a>(mut self: Self, namespace: ResolveResult, event: &Event<'a>) -> Continuation<Self, Self::Output> {
|
|
let s = Pin::new(&mut self);
|
|
// this is a very rude workaround fixing the fact that rust coroutines
|
|
// 1. don't support higher-kinded lifetimes (i.e. no `impl for <'a> Coroutine<Event<'a>>)
|
|
// 2. don't track borrows across yield points and lack thereof
|
|
// implementors of Parser should manually check that inputs are not used across yields
|
|
match s.resume(unsafe { std::mem::transmute((namespace, event)) }) {
|
|
std::ops::CoroutineState::Yielded(()) => Continuation::Continue(self),
|
|
std::ops::CoroutineState::Complete(res) => Continuation::Final(res),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The result of a single parser iteration.
|
|
pub enum Continuation<Parser, Res> {
|
|
/// The parsing is complete and the final result is available.
|
|
Final(Res),
|
|
/// The parsing is not complete and more XML events are required.
|
|
Continue(Parser),
|
|
}
|
|
|
|
pub fn parse<T: FromXml>(input: &str) -> Result<T> {
|
|
let mut reader = NsReader::from_reader(input.as_bytes());
|
|
let mut buf = vec![];
|
|
let (ns, event) = reader.read_resolved_event_into(&mut buf)?;
|
|
let mut parser: Continuation<_, std::result::Result<T, anyhow::Error>> = T::parse().consume(ns, &event);
|
|
loop {
|
|
match parser {
|
|
Continuation::Final(res) => break res,
|
|
Continuation::Continue(next) => {
|
|
let (ns, event) = reader.read_resolved_event_into(&mut buf)?;
|
|
parser = next.consume(ns, &event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! fail_fast {
|
|
($errorable: expr) => {
|
|
match $errorable {
|
|
Ok(i) => i,
|
|
Err(e) => return Continuation::Final(Err(e.into())),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! delegate_parsing {
|
|
($parser: ty, $namespace: expr, $event: expr) => {{
|
|
let mut parser = <$parser as FromXml>::parse().consume($namespace, $event);
|
|
loop {
|
|
match parser {
|
|
Continuation::Final(Ok(res)) => break Ok(res.into()),
|
|
Continuation::Final(Err(err)) => break Err(err),
|
|
Continuation::Continue(p) => {
|
|
($namespace, $event) = yield;
|
|
parser = p.consume($namespace, $event);
|
|
}
|
|
}
|
|
}
|
|
}};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! match_parser {
|
|
($name: expr, $ns: expr, $event: expr; $subtype: ty, $fin: block) => {
|
|
if $name.0 == <$subtype as FromXmlTag>::NAME.as_bytes() && $ns == ResolveResult::Bound(Namespace(<$subtype as FromXmlTag>::NS.as_bytes())) {
|
|
delegate_parsing!($subtype, $ns, $event)
|
|
} else {
|
|
$fin
|
|
}
|
|
};
|
|
($name: expr, $ns: expr, $event: expr; $subtype: ty, $($rest: ty),+, $fin: block) => {
|
|
if $name.0 == <$subtype as FromXmlTag>::NAME.as_bytes() && $ns == ResolveResult::Bound(Namespace(<$subtype as FromXmlTag>::NS.as_bytes())) {
|
|
delegate_parsing!($subtype, $ns, $event)
|
|
} else {
|
|
match_parser!($name, $ns, $event; $($rest),*, $fin)
|
|
}
|
|
};
|
|
}
|
|
|
|
pub use delegate_parsing;
|
|
pub(crate) use fail_fast;
|
|
pub use match_parser;
|