ribir_macros 0.4.0-alpha.41

A non-intrusive declarative GUI framework, to build modern native/wasm cross-platform applications.
Documentation
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, quote, quote_spanned};
use syn::{
  AngleBracketedGenericArguments, Expr, Member, Result, Token, parenthesized,
  parse::{Parse, ParseStream},
  punctuated::Punctuated,
  token::Paren,
};

use crate::{
  dollar_macro::{OriginExpr, StateExpr},
  symbol_process::*,
};

pub fn gen_part_writer(input: TokenStream, refs_ctx: Option<&mut DollarRefsCtx>) -> TokenStream {
  match syn::parse2::<PartialPartState>(input) {
    Ok(PartialPartState { id, part_state, .. }) => {
      let host = part_state.host_tokens(DollarUsedInfo::Writer, refs_ctx);
      let PartState { and_token, mutability, state, dot, part_expr, tail_dot, tail_expr } =
        part_state;
      let id = id
        .map(|id| quote! { #id.into() })
        .unwrap_or(quote! { PartialId::any() });
      let tokens = quote_spanned! { state.span() =>
        #host #dot part_writer(
          #id,
          |w| PartMut::new(#and_token #mutability w #dot #part_expr #tail_dot #tail_expr)
        )
      };
      tokens
    }
    Err(err) => err.to_compile_error(),
  }
}

pub fn gen_part_reader(input: TokenStream, refs_ctx: Option<&mut DollarRefsCtx>) -> TokenStream {
  match syn::parse2::<PartState>(input) {
    Ok(part) => {
      let host = part.host_tokens(DollarUsedInfo::Reader, refs_ctx);
      let PartState { and_token, mutability, state, dot, part_expr, tail_dot, tail_expr } = part;
      let tokens = quote_spanned! { state.span() =>
        #host #dot part_reader(
          |r| PartRef::new(#and_token #mutability r #dot #part_expr #tail_dot #tail_expr)
        )
      };
      tokens
    }
    Err(err) => err.to_compile_error(),
  }
}

pub fn gen_part_watcher(input: TokenStream, refs_ctx: Option<&mut DollarRefsCtx>) -> TokenStream {
  match syn::parse2::<PartState>(input) {
    Ok(part) => {
      let host = part.host_tokens(DollarUsedInfo::Watcher, refs_ctx);
      let PartState { and_token, mutability, state, dot, part_expr, tail_dot, tail_expr } = part;
      let tokens = quote_spanned! { state.span() =>
        #host #dot part_watcher(
          |r| PartRef::new(#and_token #mutability r #dot #part_expr #tail_dot #tail_expr)
        )
      };
      tokens
    }
    Err(err) => err.to_compile_error(),
  }
}

struct PartState {
  and_token: Option<Token![&]>,
  mutability: Option<Token![mut]>,
  state: Ident,
  dot: Token![.],
  part_expr: PartExpr,
  tail_dot: Option<Token![.]>,
  tail_expr: Option<Expr>,
}

struct PartialPartState {
  id: Option<Expr>,
  part_state: PartState,
}

enum PartExpr {
  Member(Member),
  Method {
    method: Ident,
    turbofish: Option<AngleBracketedGenericArguments>,
    paren_token: Paren,
    args: Punctuated<Expr, Token![,]>,
  },
}

impl Parse for PartialPartState {
  fn parse(input: ParseStream) -> Result<Self> {
    let part_state = input.parse();
    if let Ok(part_state) = part_state {
      return Ok(Self { id: None, part_state });
    }

    let id = input.parse()?;
    input.parse::<Token![,]>()?;
    let part_state = input.parse()?;
    Ok(PartialPartState { id: Some(id), part_state })
  }
}

impl Parse for PartState {
  fn parse(input: ParseStream) -> Result<Self> {
    let and_token = input.parse()?;
    let mutability = input.parse()?;
    let state = if input.peek(Token![self]) {
      let this = input.parse::<Token![self]>()?;
      Ident::from(this)
    } else {
      input.parse()?
    };
    let dot = input.parse()?;

    let part_expr = if input.peek2(Token![::]) || input.peek2(Paren) {
      let method = input.parse()?;
      let turbofish = if input.peek(Token![::]) {
        Some(AngleBracketedGenericArguments::parse_turbofish(input)?)
      } else {
        None
      };
      let content;
      PartExpr::Method {
        method,
        turbofish,
        paren_token: parenthesized!(content in input),
        args: content.parse_terminated(Expr::parse, Token![,])?,
      }
    } else {
      PartExpr::Member(input.parse()?)
    };

    let tail_dot = input.parse()?;
    let tail_expr = if input.is_empty() { None } else { Some(input.parse()?) };
    Ok(Self { and_token, mutability, state, dot, part_expr, tail_dot, tail_expr })
  }
}

impl PartState {
  fn host_tokens(&self, used: DollarUsedInfo, refs_ctx: Option<&mut DollarRefsCtx>) -> TokenStream {
    let mut tokens = quote! {};
    if let Some(refs_ctx) = refs_ctx {
      let state_expr = StateExpr::new(self.state.clone(), OriginExpr::Var(self.state.clone()));
      if !refs_ctx.is_capture_var(&self.state) {
        self.state.to_tokens(&mut tokens);
      } else {
        state_expr.name.to_tokens(&mut tokens);
      }
      refs_ctx.add_dollar_ref(DollarRef { state_expr, used });
    } else {
      self.state.to_tokens(&mut tokens);
    }

    tokens
  }
}

impl ToTokens for PartExpr {
  fn to_tokens(&self, tokens: &mut TokenStream) {
    match self {
      PartExpr::Member(member) => member.to_tokens(tokens),
      PartExpr::Method { method, turbofish, paren_token, args } => {
        method.to_tokens(tokens);
        turbofish.to_tokens(tokens);
        paren_token.surround(tokens, |tokens| args.to_tokens(tokens));
      }
    }
  }
}