generator-based parsing of xmpp stanzas

This commit is contained in:
Nikita Vilunov 2023-03-23 01:37:02 +01:00
parent bba1ea107d
commit d0f807841c
4 changed files with 80 additions and 105 deletions

1
rust-toolchain Normal file
View File

@ -0,0 +1 @@
nightly

View File

@ -1,3 +1,5 @@
#![feature(generators, generator_trait, type_alias_impl_trait)]
mod core; mod core;
mod prelude; mod prelude;
mod projections; mod projections;

View File

@ -18,58 +18,25 @@ pub enum IqClientBody {
Unknown(Ignore), Unknown(Ignore),
} }
#[derive(From)]
pub struct IqClientBodyParser(IqClientBodyParserInner);
#[derive(From)]
enum IqClientBodyParserInner {
Initial,
Bind(<BindRequest as FromXml>::P),
SessionV(<Session as FromXml>::P),
RosterV(<RosterQuery as FromXml>::P),
UnknownV(<Ignore as FromXml>::P),
}
impl FromXml for IqClientBody { impl FromXml for IqClientBody {
type P = IqClientBodyParser; type P = impl Parser<Output = Result<Self>>;
fn parse() -> Self::P { fn parse() -> Self::P {
IqClientBodyParserInner::Initial.into() |(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
} let bytes = match event {
} Event::Start(bytes) => bytes,
Event::Empty(bytes) => bytes,
impl Parser for IqClientBodyParser { _ => return Err(ffail!("Unexpected XML event: {event:?}")),
type Output = Result<IqClientBody>; };
let name = bytes.name();
fn consume<'a>( match_parser!(name, namespace, event;
self: Self, BindRequest,
namespace: quick_xml::name::ResolveResult, Session,
event: &quick_xml::events::Event<'a>, RosterQuery,
) -> crate::util::xml::Continuation<Self, Self::Output> { {
use IqClientBodyParserInner::*; delegate_parsing!(Ignore, namespace, event).into()
match self.0 { }
Initial => { )
let bytes = match event {
Event::Start(bytes) => bytes,
Event::Empty(bytes) => bytes,
_ => {
return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")))
}
};
let name = bytes.name();
match_parser!(IqClientBodyParser, name, namespace, event;
BindRequest,
Session,
RosterQuery,
{
IqClientBodyParser(Ignore::parse().into()).consume(namespace, event)
}
)
}
Bind(p) => delegate_parsing!(p, IqClientBodyParserInner, namespace, event),
SessionV(p) => delegate_parsing!(p, IqClientBodyParserInner, namespace, event),
RosterV(p) => delegate_parsing!(p, IqClientBodyParserInner, namespace, event),
UnknownV(p) => delegate_parsing!(p, IqClientBodyParserInner, namespace, event),
} }
} }
} }
@ -81,52 +48,27 @@ pub enum ClientPacket {
Presence(Presence<Ignore>), Presence(Presence<Ignore>),
} }
#[derive(From)]
pub struct ClientPacketParser(ClientPacketParserInner);
impl FromXml for ClientPacket { impl FromXml for ClientPacket {
type P = ClientPacketParser; type P = impl Parser<Output = Result<Self>>;
fn parse() -> Self::P { fn parse() -> Self::P {
ClientPacketParserInner::Initial.into() |(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
} let Event::Start(bytes) = event else {
} return Err(ffail!("Unexpected XML event: {event:?}"));
};
#[derive(From)] let name = bytes.name();
enum ClientPacketParserInner { match_parser!(name, namespace, event;
Initial, Iq::<IqClientBody>,
IqV(<Iq<IqClientBody> as FromXml>::P), Presence::<Ignore>,
MessageV(<Message as FromXml>::P), Message,
PresenceV(<Presence<Ignore> as FromXml>::P), {
} Err(ffail!(
"Unexpected XML event of name {:?} in namespace {:?}",
impl Parser for ClientPacketParser { name,
type Output = Result<ClientPacket>; namespace
))
fn consume<'a>( }
self: Self, )
namespace: ResolveResult,
event: &Event<'a>,
) -> Continuation<Self, Self::Output> {
use ClientPacketParserInner::*;
match self.0 {
Initial => {
let Event::Start(bytes) = event else {
return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")));
};
let name = bytes.name();
match_parser!(ClientPacketParser, name, namespace, event;
Iq::<IqClientBody>,
Presence::<Ignore>,
Message,
{
Continuation::Final(Err(ffail!("Unexpected XML event of name {:?} in namespace {:?}", name, namespace)))
}
)
}
IqV(p) => delegate_parsing!(p, ClientPacketParserInner, namespace, event),
MessageV(p) => delegate_parsing!(p, ClientPacketParserInner, namespace, event),
PresenceV(p) => delegate_parsing!(p, ClientPacketParserInner, namespace, event),
} }
} }
} }

View File

@ -1,3 +1,6 @@
use std::ops::Generator;
use std::pin::Pin;
use quick_xml::events::Event; use quick_xml::events::Event;
use quick_xml::name::ResolveResult; use quick_xml::name::ResolveResult;
@ -31,6 +34,30 @@ pub trait Parser: Sized {
) -> Continuation<Self, Self::Output>; ) -> Continuation<Self, Self::Output>;
} }
impl<T, Out> Parser for T
where
T: Generator<(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 generators
// 1. don't support higher-kinded lifetimes (i.e. no `impl for <'a> Generator<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::GeneratorState::Yielded(()) => Continuation::Continue(self),
std::ops::GeneratorState::Complete(res) => Continuation::Final(res),
}
}
}
pub enum Continuation<Parser, Res> { pub enum Continuation<Parser, Res> {
Final(Res), Final(Res),
Continue(Parser), Continue(Parser),
@ -46,31 +73,34 @@ macro_rules! fail_fast {
} }
macro_rules! delegate_parsing { macro_rules! delegate_parsing {
($parser: ident, $intermediate: ident, $namespace: expr, $event: expr) => { ($parser: ty, $namespace: expr, $event: expr) => {{
match $parser.consume($namespace, $event) { let mut parser = <$parser as FromXml>::parse().consume($namespace, $event);
Continuation::Final(Ok(r)) => Continuation::Final(Ok(r.into())), loop {
Continuation::Final(Err(e)) => Continuation::Final(Err(e)), match parser {
Continuation::Continue(s) => { Continuation::Final(Ok(res)) => break Ok(res.into()),
let inner: $intermediate = s.into(); Continuation::Final(Err(err)) => break Err(err),
Continuation::Continue(inner.into()) Continuation::Continue(p) => {
let (namespace, event) = yield;
parser = p.consume(namespace, event);
}
} }
} }
}; }};
} }
macro_rules! match_parser { macro_rules! match_parser {
($outer: ident, $name: expr, $ns: expr, $event: expr; $subtype: ty, $fin: block) => { ($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())) { if $name.0 == <$subtype as FromXmlTag>::NAME.as_bytes() && $ns == ResolveResult::Bound(Namespace(<$subtype as FromXmlTag>::NS.as_bytes())) {
$outer(<$subtype as FromXml>::parse().into()).consume($ns, $event) delegate_parsing!($subtype, $ns, $event)
} else { } else {
$fin $fin
} }
}; };
($outer: ident, $name: expr, $ns: expr, $event: expr; $subtype: ty, $($rest: ty),+, $fin: block) => { ($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())) { if $name.0 == <$subtype as FromXmlTag>::NAME.as_bytes() && $ns == ResolveResult::Bound(Namespace(<$subtype as FromXmlTag>::NS.as_bytes())) {
$outer(<$subtype as FromXml>::parse().into()).consume($ns, $event) delegate_parsing!($subtype, $ns, $event)
} else { } else {
match_parser!($outer, $name, $ns, $event; $($rest),*, $fin) match_parser!($name, $ns, $event; $($rest),*, $fin)
} }
}; };
} }