123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- //! Parse components into the VComponent VNode
- //! ==========================================
- //!
- //! This parsing path emerges from [`AmbiguousElement`] which supports validation of the vcomponent format.
- //! We can be reasonably sure that whatever enters this parsing path is in the right format.
- //! This feature must support
- //! - [x] Namespaced components
- //! - [x] Fields
- //! - [x] Componentbuilder synax
- //! - [x] Optional commas
- //! - [ ] Children
- //! - [ ] Keys
- //! - [ ] Properties spreading with with `..` syntax
- use super::*;
- use proc_macro2::TokenStream as TokenStream2;
- use quote::{quote, ToTokens, TokenStreamExt};
- use syn::{
- ext::IdentExt,
- parse::{Parse, ParseBuffer, ParseStream},
- token, Expr, ExprClosure, Ident, Result, Token,
- };
- pub struct Component {
- // accept any path-like argument
- name: syn::Path,
- body: Vec<ComponentField>,
- children: Vec<BodyNode>,
- manual_props: Option<Expr>,
- }
- impl Parse for Component {
- fn parse(stream: ParseStream) -> Result<Self> {
- // let name = s.parse::<syn::ExprPath>()?;
- // todo: look into somehow getting the crate/super/etc
- let name = syn::Path::parse_mod_style(stream)?;
- // parse the guts
- let content: ParseBuffer;
- syn::braced!(content in stream);
- let mut body: Vec<ComponentField> = Vec::new();
- let mut children: Vec<BodyNode> = Vec::new();
- let mut manual_props = None;
- parse_component_body(
- &content,
- &BodyParseConfig {
- allow_children: true,
- allow_fields: true,
- allow_manual_props: true,
- },
- &mut body,
- &mut children,
- &mut manual_props,
- )?;
- Ok(Self {
- name,
- body,
- children,
- manual_props,
- })
- }
- }
- pub struct BodyParseConfig {
- pub allow_fields: bool,
- pub allow_children: bool,
- pub allow_manual_props: bool,
- }
- // todo: unify this body parsing for both elements and components
- // both are style rather ad-hoc, though components are currently more configured
- pub fn parse_component_body(
- content: &ParseBuffer,
- cfg: &BodyParseConfig,
- body: &mut Vec<ComponentField>,
- children: &mut Vec<BodyNode>,
- manual_props: &mut Option<Expr>,
- ) -> Result<()> {
- 'parsing: loop {
- // [1] Break if empty
- if content.is_empty() {
- break 'parsing;
- }
- if content.peek(Token![..]) {
- if !cfg.allow_manual_props {
- return Err(Error::new(
- content.span(),
- "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
- ));
- }
- content.parse::<Token![..]>()?;
- *manual_props = Some(content.parse::<Expr>()?);
- } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
- if !cfg.allow_fields {
- return Err(Error::new(
- content.span(),
- "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
- ));
- }
- body.push(content.parse::<ComponentField>()?);
- } else {
- if !cfg.allow_children {
- return Err(Error::new(
- content.span(),
- "This item is not allowed to accept children.",
- ));
- }
- children.push(content.parse::<BodyNode>()?);
- }
- // consume comma if it exists
- // we don't actually care if there *are* commas between attrs
- if content.peek(Token![,]) {
- let _ = content.parse::<Token![,]>();
- }
- }
- Ok(())
- }
- impl ToTokens for Component {
- fn to_tokens(&self, tokens: &mut TokenStream2) {
- let name = &self.name;
- let mut has_key = None;
- let builder = match &self.manual_props {
- Some(manual_props) => {
- let mut toks = quote! {
- let mut __manual_props = #manual_props;
- };
- for field in &self.body {
- if field.name.to_string() == "key" {
- has_key = Some(field);
- } else {
- let name = &field.name;
- let val = &field.content;
- toks.append_all(quote! {
- __manual_props.#name = #val;
- });
- }
- }
- toks.append_all(quote! {
- __manual_props
- });
- quote! {{
- #toks
- }}
- }
- None => {
- let mut toks = quote! { fc_to_builder(#name) };
- for field in &self.body {
- if field.name.to_string() == "key" {
- has_key = Some(field);
- } else {
- toks.append_all(quote! {#field})
- }
- }
- toks.append_all(quote! {
- .build()
- });
- toks
- }
- };
- let key_token = match has_key {
- Some(field) => {
- let inners = field.content.to_token_stream();
- quote! {
- Some(#inners)
- }
- }
- None => quote! {None},
- };
- let childs = &self.children;
- let children = quote! {
- [ #( #childs ),* ]
- };
- tokens.append_all(quote! {
- __cx.component(
- #name,
- #builder,
- #key_token,
- __cx.bump().alloc(#children)
- )
- })
- }
- }
- // the struct's fields info
- pub struct ComponentField {
- name: Ident,
- content: ContentField,
- }
- enum ContentField {
- ManExpr(Expr),
- OnHandler(ExprClosure),
- // A handler was provided in {} tokens
- OnHandlerRaw(Expr),
- }
- impl ToTokens for ContentField {
- fn to_tokens(&self, tokens: &mut TokenStream2) {
- match self {
- ContentField::ManExpr(e) => e.to_tokens(tokens),
- ContentField::OnHandler(e) => tokens.append_all(quote! {
- __cx.bump().alloc(#e)
- }),
- ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
- __cx.bump().alloc(#e)
- }),
- }
- }
- }
- impl Parse for ComponentField {
- fn parse(input: ParseStream) -> Result<Self> {
- let name = Ident::parse_any(input)?;
- input.parse::<Token![:]>()?;
- let name_str = name.to_string();
- let content = if name_str.starts_with("on") {
- if input.peek(token::Brace) {
- let content;
- syn::braced!(content in input);
- ContentField::OnHandlerRaw(content.parse()?)
- } else {
- ContentField::OnHandler(input.parse()?)
- }
- } else {
- ContentField::ManExpr(input.parse::<Expr>()?)
- };
- Ok(Self { name, content })
- }
- }
- impl ToTokens for ComponentField {
- fn to_tokens(&self, tokens: &mut TokenStream2) {
- let ComponentField { name, content, .. } = self;
- tokens.append_all(quote! {
- .#name(#content)
- })
- }
- }
|