From 6f6df27f45f45e10d195aceee55653f1df2a1fc5 Mon Sep 17 00:00:00 2001 From: Adoo Date: Tue, 15 Aug 2023 11:27:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(macros):=20=F0=9F=8E=B8=20add=20a=20`watch?= =?UTF-8?q?!`=20macro=20convert=20expression=20to=20a=20`Observable`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/lib.rs | 3 ++- macros/src/lib.rs | 19 ++++++++++++++-- macros/src/pipe_macro.rs | 37 ++++++++++++++++++------------- macros/src/symbol_process.rs | 6 ++++-- macros/src/watch_macro.rs | 42 ++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 macros/src/watch_macro.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index afcf7cbed..b900d9138 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -37,6 +37,7 @@ pub mod prelude { pub use crate::dynamic_widget::*; #[doc(no_inline)] pub use crate::events::*; + pub use crate::path_state_splitter; #[doc(no_inline)] pub use crate::pipe::Pipe; #[doc(no_inline)] @@ -59,7 +60,7 @@ pub mod prelude { pub use ribir_geom::*; #[doc(no_inline)] pub use ribir_macros::{ - ctx, fn_widget, include_svg, pipe, rdl, set_build_ctx, widget, Declare, Declare2, Lerp, + ctx, fn_widget, include_svg, pipe, rdl, set_build_ctx, watch, widget, Declare, Declare2, Lerp, MultiChild, SingleChild, Template, _dollar_ಠ_ಠ, }; #[doc(no_inline)] diff --git a/macros/src/lib.rs b/macros/src/lib.rs index eb5d9b40f..f822fc1d9 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -17,9 +17,11 @@ mod child_template; mod fn_widget_macro; mod pipe_macro; mod rdl_macro; +mod watch_macro; pub(crate) use rdl_macro::*; use crate::pipe_macro::PipeExpr; +use crate::watch_macro::WatchMacro; pub(crate) mod declare_obj; pub(crate) mod symbol_process; @@ -174,8 +176,8 @@ pub fn ctx(input: TokenStream) -> TokenStream { } /// `pipe` macro use to create `Pipe` object that continuous trace the -/// expression modify. Use the `$` as an state reference and auto subscribe to -/// its modify. +/// expression modify. Use the `$` mark the state reference and auto subscribe +/// to its modify. #[proc_macro] pub fn pipe(input: TokenStream) -> TokenStream { symbol_to_macro(input).map_or_else( @@ -187,6 +189,19 @@ pub fn pipe(input: TokenStream) -> TokenStream { ) } +/// `watch!` macro use to convert a expression to a `Observable` stream. Use the +/// `$` mark the state reference and auto subscribe to its modify. +#[proc_macro] +pub fn watch(input: TokenStream) -> TokenStream { + symbol_to_macro(input).map_or_else( + |err| err, + |input| { + let expr = parse_macro_input! { input as WatchMacro }; + expr.to_token_stream().into() + }, + ) +} + /// The macro to use a state as its StateRef. Transplanted from the `$`. #[proc_macro] pub fn _dollar_ಠ_ಠ(input: TokenStream) -> TokenStream { diff --git a/macros/src/pipe_macro.rs b/macros/src/pipe_macro.rs index 83a549552..5904c811e 100644 --- a/macros/src/pipe_macro.rs +++ b/macros/src/pipe_macro.rs @@ -13,21 +13,28 @@ pub(crate) struct PipeExpr { impl Parse for PipeExpr { fn parse(input: ParseStream) -> syn::Result { - let mut refs = DollarRefs::default(); - refs.in_capture += 1; - let stmts = syn::Block::parse_within(input)?; - let stmts = stmts.into_iter().map(|s| refs.fold_stmt(s)).collect(); - refs.in_capture -= 1; - if refs.is_empty() { - let err = syn::Error::new( - input.span(), - "`pipe!` expression not subscribe anything, it must contain at least one $", - ); - Err(err) - } else { - refs.dedup(); - Ok(Self { refs, expr: stmts }) - } + let (refs, stmts) = dollar_expr_will_in_closure_parse(input)?; + Ok(Self { refs, expr: stmts }) + } +} + +pub fn dollar_expr_will_in_closure_parse( + input: ParseStream, +) -> syn::Result<(DollarRefs, Vec)> { + let mut refs = DollarRefs::default(); + refs.in_capture += 1; + let stmts = syn::Block::parse_within(input)?; + let stmts = stmts.into_iter().map(|s| refs.fold_stmt(s)).collect(); + refs.in_capture -= 1; + if refs.is_empty() { + let err = syn::Error::new( + input.span(), + "expression not subscribe anything, it must contain at least one $", + ); + Err(err) + } else { + refs.dedup(); + Ok((refs, stmts)) } } diff --git a/macros/src/symbol_process.rs b/macros/src/symbol_process.rs index bf753c20e..d0eee3995 100644 --- a/macros/src/symbol_process.rs +++ b/macros/src/symbol_process.rs @@ -182,10 +182,10 @@ impl Fold for DollarRefs { } } Expr::Closure(c) if c.capture.is_some() => { - self.in_capture += 1; let mut closure_refs = DollarRefs::default(); + closure_refs.in_capture += 1; let mut c = closure_refs.fold_expr_closure(c); - self.in_capture -= 1; + closure_refs.in_capture -= 1; if !closure_refs.is_empty() || closure_refs.ctx_used { closure_refs.dedup(); @@ -203,6 +203,8 @@ impl Fold for DollarRefs { .ctx_used .then(|| quote_spanned! { c.span() => let _ctx_handle = ctx!().handle(); }); + // todo: seems need merge `closure_refs` into `self`. + Expr::Verbatim(quote_spanned!(c.span() => { #closure_refs #handle diff --git a/macros/src/watch_macro.rs b/macros/src/watch_macro.rs new file mode 100644 index 000000000..1a22c6b56 --- /dev/null +++ b/macros/src/watch_macro.rs @@ -0,0 +1,42 @@ +use crate::{pipe_macro::dollar_expr_will_in_closure_parse, symbol_process::DollarRefs}; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + Stmt, +}; + +pub(crate) struct WatchMacro { + refs: DollarRefs, + expr: Vec, +} + +impl Parse for WatchMacro { + fn parse(input: ParseStream) -> syn::Result { + let (refs, stmts) = dollar_expr_will_in_closure_parse(input)?; + Ok(Self { refs, expr: stmts }) + } +} + +impl ToTokens for WatchMacro { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { refs, expr } = self; + + let upstream = refs.upstream_tokens(); + + if refs.used_ctx() { + quote! {{ + #refs + let _ctx_handle = ctx!().handle(); + #upstream + .map(move |_| _ctx_handle.with_ctx(|ctx!(): &BuildCtx<'_>| { #(#expr)* })) + }} + .to_tokens(tokens) + } else { + quote! {{ + #refs + #upstream.map(move |_| { #(#expr)* }) + }} + .to_tokens(tokens) + } + } +}