Skip to content

Commit

Permalink
feat(macros): 🎸 add a watch! macro convert expression to a `Observa…
Browse files Browse the repository at this point in the history
…ble`
  • Loading branch information
M-Adoo committed Aug 15, 2023
1 parent 8f5b422 commit 6f6df27
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 20 deletions.
3 changes: 2 additions & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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)]
Expand Down
19 changes: 17 additions & 2 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(
Expand All @@ -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 {
Expand Down
37 changes: 22 additions & 15 deletions macros/src/pipe_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ pub(crate) struct PipeExpr {

impl Parse for PipeExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
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<Stmt>)> {
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))
}
}

Expand Down
6 changes: 4 additions & 2 deletions macros/src/symbol_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand Down
42 changes: 42 additions & 0 deletions macros/src/watch_macro.rs
Original file line number Diff line number Diff line change
@@ -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<Stmt>,
}

impl Parse for WatchMacro {
fn parse(input: ParseStream) -> syn::Result<Self> {
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)
}
}
}

0 comments on commit 6f6df27

Please sign in to comment.