From d0f807841ca3ed7af5696eed3d36dee214da888e Mon Sep 17 00:00:00 2001 From: Nikita Vilunov Date: Thu, 23 Mar 2023 01:37:02 +0100 Subject: [PATCH] generator-based parsing of xmpp stanzas --- rust-toolchain | 1 + src/main.rs | 2 + src/projections/xmpp/proto.rs | 126 +++++++++------------------------- src/util/xml.rs | 56 +++++++++++---- 4 files changed, 80 insertions(+), 105 deletions(-) create mode 100644 rust-toolchain diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/src/main.rs b/src/main.rs index 03e8e8d..399a23f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![feature(generators, generator_trait, type_alias_impl_trait)] + mod core; mod prelude; mod projections; diff --git a/src/projections/xmpp/proto.rs b/src/projections/xmpp/proto.rs index 008c77d..6b500bd 100644 --- a/src/projections/xmpp/proto.rs +++ b/src/projections/xmpp/proto.rs @@ -18,58 +18,25 @@ pub enum IqClientBody { Unknown(Ignore), } -#[derive(From)] -pub struct IqClientBodyParser(IqClientBodyParserInner); - -#[derive(From)] -enum IqClientBodyParserInner { - Initial, - Bind(::P), - SessionV(::P), - RosterV(::P), - UnknownV(::P), -} - impl FromXml for IqClientBody { - type P = IqClientBodyParser; + type P = impl Parser>; fn parse() -> Self::P { - IqClientBodyParserInner::Initial.into() - } -} - -impl Parser for IqClientBodyParser { - type Output = Result; - - fn consume<'a>( - self: Self, - namespace: quick_xml::name::ResolveResult, - event: &quick_xml::events::Event<'a>, - ) -> crate::util::xml::Continuation { - use IqClientBodyParserInner::*; - 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), + |(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result { + let bytes = match event { + Event::Start(bytes) => bytes, + Event::Empty(bytes) => bytes, + _ => return Err(ffail!("Unexpected XML event: {event:?}")), + }; + let name = bytes.name(); + match_parser!(name, namespace, event; + BindRequest, + Session, + RosterQuery, + { + delegate_parsing!(Ignore, namespace, event).into() + } + ) } } } @@ -81,52 +48,27 @@ pub enum ClientPacket { Presence(Presence), } -#[derive(From)] -pub struct ClientPacketParser(ClientPacketParserInner); - impl FromXml for ClientPacket { - type P = ClientPacketParser; + type P = impl Parser>; fn parse() -> Self::P { - ClientPacketParserInner::Initial.into() - } -} - -#[derive(From)] -enum ClientPacketParserInner { - Initial, - IqV( as FromXml>::P), - MessageV(::P), - PresenceV( as FromXml>::P), -} - -impl Parser for ClientPacketParser { - type Output = Result; - - fn consume<'a>( - self: Self, - namespace: ResolveResult, - event: &Event<'a>, - ) -> Continuation { - 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::, - Presence::, - 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), + |(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result { + let Event::Start(bytes) = event else { + return Err(ffail!("Unexpected XML event: {event:?}")); + }; + let name = bytes.name(); + match_parser!(name, namespace, event; + Iq::, + Presence::, + Message, + { + Err(ffail!( + "Unexpected XML event of name {:?} in namespace {:?}", + name, + namespace + )) + } + ) } } } diff --git a/src/util/xml.rs b/src/util/xml.rs index 1c52549..9ad847e 100644 --- a/src/util/xml.rs +++ b/src/util/xml.rs @@ -1,3 +1,6 @@ +use std::ops::Generator; +use std::pin::Pin; + use quick_xml::events::Event; use quick_xml::name::ResolveResult; @@ -31,6 +34,30 @@ pub trait Parser: Sized { ) -> Continuation; } +impl 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 { + 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>) + // 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 { Final(Res), Continue(Parser), @@ -46,31 +73,34 @@ macro_rules! fail_fast { } macro_rules! delegate_parsing { - ($parser: ident, $intermediate: ident, $namespace: expr, $event: expr) => { - match $parser.consume($namespace, $event) { - Continuation::Final(Ok(r)) => Continuation::Final(Ok(r.into())), - Continuation::Final(Err(e)) => Continuation::Final(Err(e)), - Continuation::Continue(s) => { - let inner: $intermediate = s.into(); - Continuation::Continue(inner.into()) + ($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) => { + let (namespace, event) = yield; + parser = p.consume(namespace, event); + } } } - }; + }}; } 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())) { - $outer(<$subtype as FromXml>::parse().into()).consume($ns, $event) + delegate_parsing!($subtype, $ns, $event) } else { $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())) { - $outer(<$subtype as FromXml>::parse().into()).consume($ns, $event) + delegate_parsing!($subtype, $ns, $event) } else { - match_parser!($outer, $name, $ns, $event; $($rest),*, $fin) + match_parser!($name, $ns, $event; $($rest),*, $fin) } }; }