瀏覽代碼

Feat: custom format_args for inlining variables into html templates

Jonathan Kelley 4 年之前
父節點
當前提交
e4b1f6e

+ 1 - 0
packages/core-macro/Cargo.toml

@@ -10,6 +10,7 @@ edition = "2018"
 proc-macro = true
 
 [dependencies]
+proc-macro-hack = "0.5.19"
 proc-macro2 = "1.0.6"
 quote = "1.0"
 syn = { version = "1.0.11", features = ["full"] }

+ 322 - 0
packages/core-macro/src/fc.rs

@@ -0,0 +1,322 @@
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned, ToTokens};
+use syn::spanned::Spanned;
+use syn::{
+    parse::{Parse, ParseStream},
+    Signature,
+};
+use syn::{
+    parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
+};
+
+pub fn function_component_impl(
+    // name: FunctionComponentName,
+    component: FunctionComponent,
+) -> syn::Result<TokenStream> {
+    // let FunctionComponentName { component_name } = name;
+
+    let FunctionComponent {
+        block,
+        props_type,
+        arg,
+        vis,
+        attrs,
+        name: function_name,
+        return_type,
+    } = component;
+
+    // if function_name == component_name {
+    //     return Err(syn::Error::new_spanned(
+    //         component_name,
+    //         "the component must not have the same name as the function",
+    //     ));
+    // }
+
+    let quoted = quote! {
+        #[doc(hidden)]
+        #[allow(non_camel_case_types)]
+        mod __component_blah {
+            use super::*;
+
+            #[derive(PartialEq)]
+            pub struct Props<'a> {
+                name: &'a str
+            }
+
+            pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
+                // Destructure the props into the parent scope
+                // todo: handle expansion of lifetimes
+                let Props {
+                    name
+                } = ctx.props;
+
+                #block
+            }
+        }
+        #[allow(non_snake_case)]
+        pub use __component_blah::component as #function_name;
+    };
+    // let quoted = quote! {
+    //     #[doc(hidden)]
+    //     #[allow(non_camel_case_types)]
+    //     #vis struct #function_name;
+
+    //     impl ::yew_functional::FunctionProvider for #function_name {
+    //         type TProps = #props_type;
+
+    //         fn run(#arg) -> #ret_type {
+    //             #block
+    //         }
+    //     }
+
+    //     #(#attrs)*
+    //     #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>;
+    // };
+    Ok(quoted)
+}
+
+/// A parsed version of the user's input
+pub struct FunctionComponent {
+    // The actual contents of the function
+    block: Box<Block>,
+
+    // The user's props type
+    props_type: Box<Type>,
+
+    arg: FnArg,
+    vis: Visibility,
+    attrs: Vec<Attribute>,
+    name: Ident,
+    return_type: Box<Type>,
+}
+
+impl Parse for FunctionComponent {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let parsed: Item = input.parse()?;
+
+        // Convert the parsed input into the Function block
+        let ItemFn {
+            attrs,
+            vis,
+            sig,
+            block,
+        } = ensure_fn_block(parsed)?;
+
+        // Validate the user's signature
+        let sig = validate_signature(sig)?;
+
+        // Validate the return type is actually something
+        let return_type = ensure_return_type(sig.output)?;
+
+        // Get all the function args
+        let mut inputs = sig.inputs.into_iter();
+
+        // Collect the first arg
+        let first_arg: FnArg = inputs
+            .next()
+            .unwrap_or_else(|| syn::parse_quote! { _: &() });
+
+        // Extract the "context" object
+        let props_type = validate_context_arg(&first_arg)?;
+
+        /*
+        Extract the rest of the function arguments into a struct body
+        We require all inputs are strongly typed with names so we can destructure into the function body when expanded
+
+
+        */
+        // let rest = inputs
+        //     .map(|f| {
+        //         //
+        //         match f {
+        //             FnArg::Typed(pat) => {
+        //                 match *pat.pat {
+        //                     syn::Pat::Type(asd) => {}
+        //                     _ => {}
+        //                 };
+        //                 //
+        //             }
+        //             FnArg::Receiver(_) => {}
+        //         }
+        //         // let name = f
+        //         let stream = f.into_token_stream();
+        //         (stream)
+        //     })
+        //     .collect::<Vec<_>>();
+
+        // Collect the rest of the args into a list of definitions to be used by the inline struct
+
+        // Checking after param parsing may make it a little inefficient
+        // but that's a requirement for better error messages in case of receivers
+        // `>0` because first one is already consumed.
+        // if inputs.len() > 0 {
+        //     let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect();
+        //     return Err(syn::Error::new_spanned(
+        //         params,
+        //         "function components can accept at most one parameter for the props",
+        //     ));
+        // }
+        let name = sig.ident;
+
+        Ok(Self {
+            props_type,
+            block,
+            arg: first_arg,
+            vis,
+            attrs,
+            name,
+            return_type,
+        })
+    }
+}
+
+/// Ensure the user's input is actually a functional component
+pub fn ensure_fn_block(item: Item) -> syn::Result<ItemFn> {
+    match item {
+        Item::Fn(it) => Ok(it),
+        Item::Static(it) => {
+            let syn::ItemStatic {
+                attrs,
+                vis,
+                static_token,
+                mutability,
+                ident,
+                colon_token,
+                ty,
+                eq_token,
+                expr,
+                semi_token,
+            } = &it;
+            match ty.as_ref() {
+                Type::BareFn(bare) => {}
+                // Type::Array(_)
+                // | Type::Group(_)
+                // | Type::ImplTrait(_)
+                // | Type::Infer(_)
+                // | Type::Macro(_)
+                // | Type::Never(_)
+                // | Type::Paren(_)
+                // | Type::Path(_)
+                // | Type::Ptr(_)
+                // | Type::Reference(_)
+                // | Type::Slice(_)
+                // | Type::TraitObject(_)
+                // | Type::Tuple(_)
+                // | Type::Verbatim(_)
+                _ => {}
+            };
+
+            // TODO: Add support for static block
+            // Ensure that the contents of the static block can be extracted to a function
+            // TODO: @Jon
+            // Decide if statics should be converted to functions (under the hood) or stay as statics
+            // They _do_ get promoted, but also have a &'static ref
+            Err(syn::Error::new_spanned(
+                it,
+                "`function_component` attribute not ready for statics",
+            ))
+        }
+        other => Err(syn::Error::new_spanned(
+            other,
+            "`function_component` attribute can only be applied to functions",
+        )),
+    }
+}
+
+/// Ensure the user's function actually returns a VNode
+pub fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
+    match output {
+        ReturnType::Default => Err(syn::Error::new_spanned(
+            output,
+            "function components must return `dioxus::VNode`",
+        )),
+        ReturnType::Type(_, ty) => Ok(ty),
+    }
+}
+
+/// Validate the users's input signature for the function component.
+/// Returns an error if any of the conditions prove to be wrong;
+pub fn validate_signature(sig: Signature) -> syn::Result<Signature> {
+    if !sig.generics.params.is_empty() {
+        return Err(syn::Error::new_spanned(
+            sig.generics,
+            "function components can't contain generics",
+        ));
+    }
+
+    if sig.asyncness.is_some() {
+        return Err(syn::Error::new_spanned(
+            sig.asyncness,
+            "function components can't be async",
+        ));
+    }
+
+    if sig.constness.is_some() {
+        return Err(syn::Error::new_spanned(
+            sig.constness,
+            "const functions can't be function components",
+        ));
+    }
+
+    if sig.abi.is_some() {
+        return Err(syn::Error::new_spanned(
+            sig.abi,
+            "extern functions can't be function components",
+        ));
+    }
+
+    Ok(sig)
+}
+
+pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
+    if let FnArg::Typed(arg) = first_arg {
+        // Input arg is a reference to an &mut Context
+        if let Type::Reference(ty) = &*arg.ty {
+            if ty.lifetime.is_some() {
+                return Err(syn::Error::new_spanned(
+                    &ty.lifetime,
+                    "reference must not have a lifetime",
+                ));
+            }
+
+            if ty.mutability.is_some() {
+                return Err(syn::Error::new_spanned(
+                    &ty.mutability,
+                    "reference must not be mutable",
+                ));
+            }
+
+            Ok(ty.elem.clone())
+        } else {
+            let msg = format!(
+                "expected a reference to a `Context` object (try: `&mut {}`)",
+                arg.ty.to_token_stream()
+            );
+            return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
+        }
+    } else {
+        return Err(syn::Error::new_spanned(
+            first_arg,
+            "function components can't accept a receiver",
+        ));
+    }
+}
+
+pub fn collect_inline_args() {}
+
+/// The named specified in the macro usage.
+pub struct FunctionComponentName {
+    component_name: Ident,
+}
+
+impl Parse for FunctionComponentName {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        if input.is_empty() {
+            return Err(input.error("expected identifier for the component"));
+        }
+
+        let component_name = input.parse()?;
+
+        Ok(Self { component_name })
+    }
+}

+ 271 - 0
packages/core-macro/src/ifmt.rs

@@ -0,0 +1,271 @@
+use ::proc_macro::TokenStream;
+use ::quote::{quote, ToTokens};
+use ::std::ops::Not;
+use ::syn::{
+    parse::{Parse, ParseStream},
+    punctuated::Punctuated,
+    *,
+};
+use proc_macro2::TokenStream as TokenStream2;
+
+// #[macro_use]
+// mod macros {
+
+// #[cfg(not(feature = "verbose-expansions"))]
+macro_rules! debug_input {
+    ($expr:expr) => {
+        $expr
+    };
+}
+
+// #[cfg(feature = "verbose-expansions")]
+macro_rules! debug_input {
+    ($expr:expr) => {
+        match $expr {
+            expr => {
+                eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr);
+                expr
+            }
+        }
+    };
+}
+
+// #[cfg(not(feature = "verbose-expansions"))]
+macro_rules! debug_output {
+    ($expr:expr) => {
+        $expr
+    };
+}
+
+// #[cfg(feature = "verbose-expansions")]
+macro_rules! debug_output {
+    ($expr:expr) => {
+        match $expr {
+            expr => {
+                eprintln!("=>\n{}\n-------------------\n", expr);
+                expr
+            }
+        }
+    };
+}
+// }
+
+pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
+    let IfmtInput {
+        mut format_literal,
+        mut positional_args,
+        mut named_args,
+    } = input;
+
+    let s = format_literal.value();
+    let ref mut out_format_literal = String::with_capacity(s.len());
+
+    let mut iterator = s.char_indices().peekable();
+    while let Some((i, c)) = iterator.next() {
+        out_format_literal.push(c);
+        if c != '{' {
+            continue;
+        }
+        // encountered `{`, let's see if it was `{{`
+        if let Some(&(_, '{')) = iterator.peek() {
+            let _ = iterator.next();
+            out_format_literal.push('{');
+            continue;
+        }
+        let (end, colon_or_closing_brace) =
+            iterator
+                .find(|&(_, c)| c == '}' || c == ':')
+                .expect(concat!(
+                    "Invalid format string literal\n",
+                    "note: if you intended to print `{`, ",
+                    "you can escape it using `{{`",
+                ));
+        // We use defer to ensure all the `continue`s append the closing char.
+        let mut out_format_literal = defer(&mut *out_format_literal, |it| {
+            it.push(colon_or_closing_brace)
+        });
+        let out_format_literal: &mut String = &mut *out_format_literal;
+        let mut arg = s[i + 1..end].trim();
+        if let Some("=") = arg.get(arg.len().saturating_sub(1)..) {
+            assert_eq!(
+                out_format_literal.pop(), // Remove the opening brace
+                Some('{'),
+            );
+            arg = &arg[..arg.len() - 1];
+            out_format_literal.push_str(arg);
+            out_format_literal.push_str(" = {");
+        }
+        if arg.is_empty() {
+            continue;
+        }
+
+        enum Segment {
+            Ident(Ident),
+            LitInt(LitInt),
+        }
+        let segments: Vec<Segment> = {
+            impl Parse for Segment {
+                fn parse(input: ParseStream<'_>) -> Result<Self> {
+                    let lookahead = input.lookahead1();
+                    if lookahead.peek(Ident) {
+                        input.parse().map(Segment::Ident)
+                    } else if lookahead.peek(LitInt) {
+                        input.parse().map(Segment::LitInt)
+                    } else {
+                        Err(lookahead.error())
+                    }
+                }
+            }
+            match ::syn::parse::Parser::parse_str(
+                Punctuated::<Segment, Token![.]>::parse_separated_nonempty,
+                arg,
+            ) {
+                Ok(segments) => segments.into_iter().collect(),
+                Err(err) => return err.to_compile_error().into(),
+            }
+        };
+        match segments.len() {
+            0 => unreachable!("`parse_separated_nonempty` returned empty"),
+            1 => {
+                out_format_literal.push_str(arg);
+                match { segments }.pop().unwrap() {
+                    Segment::LitInt(_) => {
+                        // found something like `{0}`, let `format_args!`
+                        // handle it.
+                        continue;
+                    }
+                    Segment::Ident(ident) => {
+                        // if `ident = ...` is not yet among the extra args
+                        if named_args.iter().all(|(it, _)| *it != ident) {
+                            named_args.push((
+                                ident.clone(),
+                                parse_quote!(#ident), // Expr
+                            ));
+                        }
+                    }
+                }
+            }
+            _ => {
+                ::std::fmt::Write::write_fmt(
+                    out_format_literal,
+                    format_args!("{}", positional_args.len()),
+                )
+                .expect("`usize` or `char` Display impl cannot panic");
+                let segments: Punctuated<TokenStream2, Token![.]> = segments
+                    .into_iter()
+                    .map(|it| match it {
+                        Segment::Ident(ident) => ident.into_token_stream(),
+                        Segment::LitInt(literal) => literal.into_token_stream(),
+                    })
+                    .collect();
+                positional_args.push(parse_quote! {
+                    #segments
+                })
+            }
+        }
+    }
+
+    let named_args = named_args.into_iter().map(|(ident, expr)| {
+        quote! {
+            #ident = #expr
+        }
+    });
+    format_literal = LitStr::new(out_format_literal, format_literal.span());
+
+    TokenStream::from(debug_output!(quote! {
+        format_args!(
+            #format_literal
+            #(, #positional_args)*
+            #(, #named_args)*
+        )
+    }))
+}
+
+#[allow(dead_code)] // dumb compiler does not see the struct being used...
+pub struct IfmtInput {
+    format_literal: LitStr,
+    positional_args: Vec<Expr>,
+    named_args: Vec<(Ident, Expr)>,
+}
+
+impl Parse for IfmtInput {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let format_literal = input.parse()?;
+        let mut positional_args = vec![];
+        loop {
+            if input.parse::<Option<Token![,]>>()?.is_none() {
+                return Ok(Self {
+                    format_literal,
+                    positional_args,
+                    named_args: vec![],
+                });
+            }
+            if input.peek(Ident) && input.peek2(Token![=]) && input.peek3(Token![=]).not() {
+                // Found a positional parameter
+                break;
+            }
+            positional_args.push(input.parse()?);
+        }
+        let named_args = Punctuated::<_, Token![,]>::parse_terminated_with(input, |input| {
+            Ok({
+                let name: Ident = input.parse()?;
+                let _: Token![=] = input.parse()?;
+                let expr: Expr = input.parse()?;
+                (name, expr)
+            })
+        })?
+        .into_iter()
+        .collect();
+        Ok(Self {
+            format_literal,
+            positional_args,
+            named_args,
+        })
+    }
+}
+
+pub fn defer<'a, T: 'a, Drop: 'a>(x: T, drop: Drop) -> impl ::core::ops::DerefMut<Target = T> + 'a
+where
+    Drop: FnOnce(T),
+{
+    use ::core::mem::ManuallyDrop;
+    struct Ret<T, Drop>(ManuallyDrop<T>, ManuallyDrop<Drop>)
+    where
+        Drop: FnOnce(T);
+    impl<T, Drop> ::core::ops::Drop for Ret<T, Drop>
+    where
+        Drop: FnOnce(T),
+    {
+        fn drop(self: &'_ mut Self) {
+            use ::core::ptr;
+            unsafe {
+                // # Safety
+                //
+                //   - This is the canonical example of using `ManuallyDrop`.
+                let value = ManuallyDrop::into_inner(ptr::read(&mut self.0));
+                let drop = ManuallyDrop::into_inner(ptr::read(&mut self.1));
+                drop(value);
+            }
+        }
+    }
+    impl<T, Drop> ::core::ops::Deref for Ret<T, Drop>
+    where
+        Drop: FnOnce(T),
+    {
+        type Target = T;
+        #[inline]
+        fn deref(self: &'_ Self) -> &'_ Self::Target {
+            &self.0
+        }
+    }
+    impl<T, Drop> ::core::ops::DerefMut for Ret<T, Drop>
+    where
+        Drop: FnOnce(T),
+    {
+        #[inline]
+        fn deref_mut(self: &'_ mut Self) -> &'_ mut Self::Target {
+            &mut self.0
+        }
+    }
+    Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop))
+}

+ 17 - 308
packages/core-macro/src/lib.rs

@@ -1,4 +1,3 @@
-use proc_macro2::TokenStream;
 use quote::{quote, quote_spanned, ToTokens};
 use syn::spanned::Spanned;
 use syn::{
@@ -9,327 +8,37 @@ use syn::{
     parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
 };
 
+mod fc;
+mod ifmt;
+
 /// Label a function or static closure as a functional component.
 /// This macro reduces the need to create a separate properties struct.
 #[proc_macro_attribute]
 pub fn fc(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    use fc::{function_component_impl, FunctionComponent};
+
     let item = parse_macro_input!(item as FunctionComponent);
-    // let attr = parse_macro_input!(attr as FunctionComponentName);
 
     function_component_impl(item)
-        // function_component_impl(attr, item)
         .unwrap_or_else(|err| err.to_compile_error())
         .into()
-}
-
-fn function_component_impl(
-    // name: FunctionComponentName,
-    component: FunctionComponent,
-) -> syn::Result<TokenStream> {
-    // let FunctionComponentName { component_name } = name;
-
-    let FunctionComponent {
-        block,
-        props_type,
-        arg,
-        vis,
-        attrs,
-        name: function_name,
-        return_type,
-    } = component;
-
-    // if function_name == component_name {
-    //     return Err(syn::Error::new_spanned(
-    //         component_name,
-    //         "the component must not have the same name as the function",
-    //     ));
-    // }
-
-    let quoted = quote! {
-        #[doc(hidden)]
-        #[allow(non_camel_case_types)]
-        mod __component_blah {
-            use super::*;
-
-            #[derive(PartialEq)]
-            pub struct Props<'a> {
-                name: &'a str
-            }
-
-            pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
-                // Destructure the props into the parent scope
-                // todo: handle expansion of lifetimes
-                let Props {
-                    name
-                } = ctx.props;
-
-                #block
-            }
-        }
-        #[allow(non_snake_case)]
-        pub use __component_blah::component as #function_name;
-    };
-    // let quoted = quote! {
-    //     #[doc(hidden)]
-    //     #[allow(non_camel_case_types)]
-    //     #vis struct #function_name;
-
-    //     impl ::yew_functional::FunctionProvider for #function_name {
-    //         type TProps = #props_type;
-
-    //         fn run(#arg) -> #ret_type {
-    //             #block
-    //         }
-    //     }
-
-    //     #(#attrs)*
-    //     #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>;
-    // };
-    Ok(quoted)
-}
-
-/// A parsed version of the user's input
-struct FunctionComponent {
-    // The actual contents of the function
-    block: Box<Block>,
-
-    // The user's props type
-    props_type: Box<Type>,
-
-    arg: FnArg,
-    vis: Visibility,
-    attrs: Vec<Attribute>,
-    name: Ident,
-    return_type: Box<Type>,
-}
-
-impl Parse for FunctionComponent {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        let parsed: Item = input.parse()?;
-
-        // Convert the parsed input into the Function block
-        let ItemFn {
-            attrs,
-            vis,
-            sig,
-            block,
-        } = ensure_fn_block(parsed)?;
-
-        // Validate the user's signature
-        let sig = validate_signature(sig)?;
-
-        // Validate the return type is actually something
-        let return_type = ensure_return_type(sig.output)?;
-
-        // Get all the function args
-        let mut inputs = sig.inputs.into_iter();
 
-        // Collect the first arg
-        let first_arg: FnArg = inputs
-            .next()
-            .unwrap_or_else(|| syn::parse_quote! { _: &() });
-
-        // Extract the "context" object
-        let props_type = validate_context_arg(&first_arg)?;
-
-        /*
-        Extract the rest of the function arguments into a struct body
-        We require all inputs are strongly typed with names so we can destructure into the function body when expanded
-
-
-        */
-        // let rest = inputs
-        //     .map(|f| {
-        //         //
-        //         match f {
-        //             FnArg::Typed(pat) => {
-        //                 match *pat.pat {
-        //                     syn::Pat::Type(asd) => {}
-        //                     _ => {}
-        //                 };
-        //                 //
-        //             }
-        //             FnArg::Receiver(_) => {}
-        //         }
-        //         // let name = f
-        //         let stream = f.into_token_stream();
-        //         (stream)
-        //     })
-        //     .collect::<Vec<_>>();
-
-        // Collect the rest of the args into a list of definitions to be used by the inline struct
-
-        // Checking after param parsing may make it a little inefficient
-        // but that's a requirement for better error messages in case of receivers
-        // `>0` because first one is already consumed.
-        // if inputs.len() > 0 {
-        //     let params: TokenStream = inputs.map(|it| it.to_token_stream()).collect();
-        //     return Err(syn::Error::new_spanned(
-        //         params,
-        //         "function components can accept at most one parameter for the props",
-        //     ));
-        // }
-        let name = sig.ident;
-
-        Ok(Self {
-            props_type,
-            block,
-            arg: first_arg,
-            vis,
-            attrs,
-            name,
-            return_type,
-        })
-    }
-}
-
-/// Ensure the user's input is actually a functional component
-fn ensure_fn_block(item: Item) -> syn::Result<ItemFn> {
-    match item {
-        Item::Fn(it) => Ok(it),
-        Item::Static(it) => {
-            let syn::ItemStatic {
-                attrs,
-                vis,
-                static_token,
-                mutability,
-                ident,
-                colon_token,
-                ty,
-                eq_token,
-                expr,
-                semi_token,
-            } = &it;
-            match ty.as_ref() {
-                Type::BareFn(bare) => {}
-                // Type::Array(_)
-                // | Type::Group(_)
-                // | Type::ImplTrait(_)
-                // | Type::Infer(_)
-                // | Type::Macro(_)
-                // | Type::Never(_)
-                // | Type::Paren(_)
-                // | Type::Path(_)
-                // | Type::Ptr(_)
-                // | Type::Reference(_)
-                // | Type::Slice(_)
-                // | Type::TraitObject(_)
-                // | Type::Tuple(_)
-                // | Type::Verbatim(_)
-                _ => {}
-            };
-
-            // TODO: Add support for static block
-            // Ensure that the contents of the static block can be extracted to a function
-            // TODO: @Jon
-            // Decide if statics should be converted to functions (under the hood) or stay as statics
-            // They _do_ get promoted, but also have a &'static ref
-            Err(syn::Error::new_spanned(
-                it,
-                "`function_component` attribute not ready for statics",
-            ))
-        }
-        other => Err(syn::Error::new_spanned(
-            other,
-            "`function_component` attribute can only be applied to functions",
-        )),
-    }
-}
-
-/// Ensure the user's function actually returns a VNode
-fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
-    match output {
-        ReturnType::Default => Err(syn::Error::new_spanned(
-            output,
-            "function components must return `dioxus::VNode`",
-        )),
-        ReturnType::Type(_, ty) => Ok(ty),
-    }
-}
-
-/// Validate the users's input signature for the function component.
-/// Returns an error if any of the conditions prove to be wrong;
-fn validate_signature(sig: Signature) -> syn::Result<Signature> {
-    if !sig.generics.params.is_empty() {
-        return Err(syn::Error::new_spanned(
-            sig.generics,
-            "function components can't contain generics",
-        ));
-    }
-
-    if sig.asyncness.is_some() {
-        return Err(syn::Error::new_spanned(
-            sig.asyncness,
-            "function components can't be async",
-        ));
-    }
-
-    if sig.constness.is_some() {
-        return Err(syn::Error::new_spanned(
-            sig.constness,
-            "const functions can't be function components",
-        ));
-    }
-
-    if sig.abi.is_some() {
-        return Err(syn::Error::new_spanned(
-            sig.abi,
-            "extern functions can't be function components",
-        ));
-    }
-
-    Ok(sig)
-}
-
-fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
-    if let FnArg::Typed(arg) = first_arg {
-        // Input arg is a reference to an &mut Context
-        if let Type::Reference(ty) = &*arg.ty {
-            if ty.lifetime.is_some() {
-                return Err(syn::Error::new_spanned(
-                    &ty.lifetime,
-                    "reference must not have a lifetime",
-                ));
-            }
-
-            if ty.mutability.is_some() {
-                return Err(syn::Error::new_spanned(
-                    &ty.mutability,
-                    "reference must not be mutable",
-                ));
-            }
-
-            Ok(ty.elem.clone())
-        } else {
-            let msg = format!(
-                "expected a reference to a `Context` object (try: `&mut {}`)",
-                arg.ty.to_token_stream()
-            );
-            return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
-        }
-    } else {
-        return Err(syn::Error::new_spanned(
-            first_arg,
-            "function components can't accept a receiver",
-        ));
-    }
+    // function_component_impl(attr, item)
+    // let attr = parse_macro_input!(attr as FunctionComponentName);
 }
 
-fn collect_inline_args() {}
+#[proc_macro]
+pub fn format_args_f(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    use ifmt::*;
 
-/// The named specified in the macro usage.
-struct FunctionComponentName {
-    component_name: Ident,
-}
+    let item = parse_macro_input!(input as IfmtInput);
 
-impl Parse for FunctionComponentName {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        if input.is_empty() {
-            return Err(input.error("expected identifier for the component"));
-        }
+    // #[allow(unused)]
+    // const FUNCTION_NAME: &str = "format_args_f";
 
-        let component_name = input.parse()?;
+    // debug_input!(&input);
 
-        Ok(Self { component_name })
-    }
+    ifmt::format_args_f_impl(item)
+    // .unwrap_or_else(|err| err.to_compile_error())
+    // .into()
 }

+ 1 - 0
packages/core/Cargo.toml

@@ -34,5 +34,6 @@ longest-increasing-subsequence = "0.1.0"
 serde = { version = "1.0.123", features = ["derive"] }
 log = "0.4.14"
 pretty_env_logger = "0.4.0"
+fstrings = "0.2.3"
 # ouroboros = "0.8.0"
 # hashbrown = { version = "0.9.1", features = ["bumpalo"] }

+ 67 - 0
packages/core/examples/fmter.rs

@@ -0,0 +1,67 @@
+// #[macro_use]
+extern crate fstrings;
+
+use bumpalo::collections::String;
+
+// use dioxus_core::ifmt;
+// use fstrings::format_args_f;
+use bumpalo::core_alloc::fmt::Write;
+
+fn main() {
+    let bump = bumpalo::Bump::new();
+    let b = &bump;
+    let world = "123";
+    // dioxus_core::ifmt!(in b; "Hello {world}";);
+}
+
+// let mut s = bumpalo::collections::String::new_in(b);
+// fstrings::write_f!(s, "Hello {world}");
+// dbg!(s);
+// let p = {
+//     println!("hello, {}", &world);
+//     ()
+// };
+// let g = format_args!("hello {world}", world = world);
+// let g = dioxus_core::ifmt!(in b, "Hello {world}");
+// let g = ifmt!(in b, "hhello {world}");
+// let g = ::core::fmt::Arguments::new_v1(
+//     &["hello "],
+//     &match (&world,) {
+//         (arg0,) => [::core::fmt::ArgumentV1::new(
+//             arg0,
+//             ::core::fmt::Display::fmt,
+//         )],
+//     },
+// );
+// fn main() {
+//     let bump = bumpalo::Bump::new();
+//     let b = &bump;
+//     let world = "123";
+//     let world = 123;
+//     let g = {
+//         use bumpalo::core_alloc::fmt::Write;
+//         use ::dioxus_core::prelude::bumpalo;
+//         use ::dioxus_core::prelude::format_args_f;
+//         let bump = b;
+//         let mut s = bumpalo::collections::String::new_in(bump);
+//         let _ = (&mut s).write_fmt(::core::fmt::Arguments::new_v1(
+//             &[""],
+//             &match (&::core::fmt::Arguments::new_v1(
+//                 &["hhello "],
+//                 &match (&world,) {
+//                     (arg0,) => [::core::fmt::ArgumentV1::new(
+//                         arg0,
+//                         ::core::fmt::Display::fmt,
+//                     )],
+//                 },
+//             ),)
+//             {
+//                 (arg0,) => [::core::fmt::ArgumentV1::new(
+//                     arg0,
+//                     ::core::fmt::Display::fmt,
+//                 )],
+//             },
+//         ));
+//         s
+//     };
+// }

+ 11 - 12
packages/core/examples/listener.rs

@@ -2,23 +2,22 @@
 
 use dioxus_core::prelude::*;
 
-fn main() {}
+fn main() {
+    Some(10)
+        .map(|f| f * 5)
+        .map(|f| f / 3)
+        .map(|f| f * 5)
+        .map(|f| f / 3);
+}
 
-/*
-Our flagship demo :)
-
-*/
 static Example: FC<()> = |ctx, props| {
-    let (val1, set_val1) = use_state(&ctx, || "b1");
+    let (name, set_name) = use_state(&ctx, || "...?");
 
     ctx.view(html! {
         <div>
-            <button onclick={move |_| set_val1("b1")}> "Set value to b1" </button>
-            <button onclick={move |_| set_val1("b2")}> "Set value to b2" </button>
-            <button onclick={move |_| set_val1("b3")}> "Set value to b3" </button>
-            <div>
-                <p> "Value is: {val1}" </p>
-            </div>
+            <h1> "Hello, {name}" </h1>
+            <button onclick={move |_| set_name("jack")}> "jack!" </button>
+            <button onclick={move |_| set_name("jill")}> "jill!" </button>
         </div>
     })
 };

+ 118 - 1
packages/core/src/lib.rs

@@ -142,7 +142,8 @@ pub mod prelude {
     // Re-export the FC macro
     pub use crate as dioxus;
     pub use crate::nodebuilder as builder;
-    pub use dioxus_core_macro::fc;
+    // pub use dioxus_core_macro::fc;
+    pub use dioxus_core_macro::format_args_f;
     pub use dioxus_html_2::html;
 
     // pub use crate::diff::DiffMachine;
@@ -150,3 +151,119 @@ pub mod prelude {
 
     pub use crate::hooks::*;
 }
+
+// #[macro_use]
+// extern crate dioxus_core_macro;
+
+// #[macro_use]
+// extern crate fstrings;
+// pub use dioxus_core_macro::format_args_f;
+// macro_rules! mk_macros {( @with_dollar![$dol:tt]=>
+//     $(
+//         #[doc = $doc_string:literal]
+//         $printlnf:ident
+//             => $println:ident!($($stream:ident,)? ...)
+//         ,
+//     )*
+// ) => (
+//     $(
+//         #[doc = $doc_string]
+//         #[macro_export]
+//         macro_rules! $printlnf {(
+//             $($dol $stream : expr,)? $dol($dol args:tt)*
+//         ) => (
+//             $println!($($dol $stream,)? "{}", format_args_f!($dol($dol args)*))
+//         )}
+//     )*
+// )}
+
+// mk_macros! { @with_dollar![$]=>
+//     #[doc = "Like [`print!`](https://doc.rust-lang.org/std/macro.print.html), but with basic f-string interpolation."]
+//     print_f
+//         => print!(...)
+//     ,
+//     #[doc = "Like [`println!`](https://doc.rust-lang.org/std/macro.println.html), but with basic f-string interpolation."]
+//     println_f
+//         => println!(...)
+//     ,
+//     #[doc = "Like [`eprint!`](https://doc.rust-lang.org/std/macro.eprint.html), but with basic f-string interpolation."]
+//     eprint_f
+//         => eprint!(...)
+//     ,
+//     #[doc = "Like [`eprintln!`](https://doc.rust-lang.org/std/macro.eprintln.html), but with basic f-string interpolation."]
+//     eprintln_f
+//         => eprintln!(...)
+//     ,
+//     #[doc = "Like [`format!`](https://doc.rust-lang.org/std/macro.format.html), but with basic f-string interpolation."]
+//     format_f
+//         => format!(...)
+//     ,
+//     #[doc = "Shorthand for [`format_f`]."]
+//     f
+//         => format!(...)
+//     ,
+//     #[doc = "Like [`panic!`](https://doc.rust-lang.org/std/macro.panic.html), but with basic f-string interpolation."]
+//     panic_f
+//         => panic!(...)
+//     ,
+//     #[doc = "Like [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html), but with basic f-string interpolation."]
+//     unreachable_f
+//         => unreachable!(...)
+//     ,
+//     #[doc = "Like [`unimplemented!`](https://doc.rust-lang.org/std/macro.unimplemented.html), but with basic f-string interpolation."]
+//     unimplemented_f
+//         => unimplemented!(...)
+//     ,
+//     #[doc = "Like [`write!`](https://doc.rust-lang.org/std/macro.write.html), but with basic f-string interpolation."]
+//     write_f
+//         => write!(stream, ...)
+//     ,
+//     #[doc = "Like [`writeln!`](https://doc.rust-lang.org/std/macro.writeln.html), but with basic f-string interpolation."]
+//     writeln_f
+//         => writeln!(stream, ...)
+//     ,
+// }
+/// Like the `format!` macro for creating `std::string::String`s but for
+/// `bumpalo::collections::String`.
+///
+/// # Examples
+///
+/// ```
+/// use bumpalo::Bump;
+///
+/// let b = Bump::new();
+///
+/// let who = "World";
+/// let s = bumpalo::format!(in &b, "Hello, {}!", who);
+/// assert_eq!(s, "Hello, World!")
+/// ```
+#[macro_export]
+macro_rules! ifmt {
+    ( in $bump:expr; $fmt:literal;) => {{
+        use bumpalo::core_alloc::fmt::Write;
+        use $crate::prelude::bumpalo;
+        let bump = $bump;
+        let mut s = bumpalo::collections::String::new_in(bump);
+        let args = $crate::prelude::format_args_f!($fmt);
+        s.write_fmt(args);
+        s
+    }};
+}
+// ( in $bump:expr; $fmt:expr; ) => {
+// $println!("{}", format_args_f!($dol($dol args)*))
+
+// write!(&mut s, println!("{}", args));
+// let _ = $crate::write_f!(&mut s, $fmt);
+// s
+//     use fstrings::*;
+//     $crate::ifmt!(in $bump, $fmt)
+// };
+
+#[test]
+fn macro_test() {
+    let w = 123;
+    let world = &w;
+    // let g = format_args_f!("Hello {world}");
+
+    // dbg!(g);
+}

+ 16 - 9
packages/core/src/nodebuilder.rs

@@ -1,11 +1,14 @@
 //! Helpers for building virtual DOM VNodes.
 
+use std::ops::Deref;
+
 use crate::{
     innerlude::VComponent,
     nodes::{Attribute, Listener, NodeKey, VNode},
     prelude::VElement,
 };
 
+use bumpalo::format;
 use bumpalo::Bump;
 
 /// A virtual DOM element builder.
@@ -335,15 +338,11 @@ where
     ///     .finish();
     /// ```
     #[inline]
-    pub fn on(self, _event: &'a str, _callback: impl Fn(()) + 'a) -> Self
-// pub fn on<F>(mut self, event: &'a str, callback: impl Fn(()) -> () + 'static) -> Self
-// F: Fn(()) + 'static,
-        // F: Fn(()) + 'a,
-    {
-        // self.listeners.push(Listener {
-        //     event,
-        //     callback: self.bump.alloc(callback),
-        // });
+    pub fn on(mut self, event: &'static str, callback: impl Fn(()) + 'a) -> Self {
+        self.listeners.push(Listener {
+            event,
+            callback: self.bump.alloc(callback),
+        });
         self
     }
 }
@@ -1038,6 +1037,14 @@ pub fn text<'a>(contents: &'a str) -> VNode<'a> {
     VNode::text(contents)
 }
 
+pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
+    let f: &'a str = contents.into_bump_str();
+    VNode::text(f)
+}
+// pub fn text<'a>(contents: &'a str) -> VNode<'a> {
+//     VNode::text(contents)
+// }
+
 /// Construct an attribute for an element.
 ///
 /// # Example

+ 3 - 0
packages/core/src/scope.rs

@@ -222,6 +222,9 @@ impl ActiveFrame {
 // #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::prelude::bumpalo;
+    // use crate::prelude::bumpalo::collections::string::String;
+    use crate::prelude::format_args_f;
 
     static ListenerTest: FC<()> = |ctx, props| {
         ctx.view(html! {

+ 18 - 2
packages/html-macro-2/src/lib.rs

@@ -4,7 +4,7 @@
 //! - [ ] Support for inline format in text
 //! - [ ] Support for expressions in attribute positions
 //! - [ ] Support for iterators
-//!
+//! - [ ] support for inline html!
 //!
 //!
 //!
@@ -80,6 +80,9 @@ impl ToTokens for HtmlRender {
 /// =============================================
 /// Parse any child as a node or list of nodes
 /// =============================================
+/// - [ ] Allow iterators
+///
+///
 enum NodeOrList {
     Node(Node),
     List(NodeList),
@@ -140,6 +143,9 @@ impl Parse for Node {
 /// =======================================
 /// Parse the VNode::Element type
 /// =======================================
+/// - [ ] Allow VComponent
+///
+///
 struct Element {
     name: Ident,
     attrs: Vec<Attr>,
@@ -242,6 +248,9 @@ impl Parse for Element {
 /// =======================================
 /// Parse a VElement's Attributes
 /// =======================================
+/// - [ ] Allow expressions as attribute
+///
+///
 struct Attr {
     name: Ident,
     ty: AttrType,
@@ -316,6 +325,8 @@ enum AttrType {
 /// =======================================
 /// Parse just plain text
 /// =======================================
+/// - [ ] Perform formatting automatically
+///
 ///
 struct TextNode(MaybeExpr<LitStr>);
 
@@ -330,7 +341,12 @@ impl ToTokens for ToToksCtx<&TextNode> {
         let mut token_stream = TokenStream2::new();
         self.recurse(&self.inner.0).to_tokens(&mut token_stream);
         tokens.append_all(quote! {
-            dioxus::builder::text(#token_stream)
+            {
+                use bumpalo::core_alloc::fmt::Write;
+                let mut s = bumpalo::collections::String::new_in(bump);
+                s.write_fmt(format_args_f!(#token_stream)).unwrap();
+                dioxus::builder::text2(s)
+            }
         });
     }
 }

+ 38 - 0
packages/web/examples/jackjill.rs

@@ -0,0 +1,38 @@
+use dioxus::prelude::bumpalo;
+use dioxus::prelude::format_args_f;
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::html;
+use dioxus_web::WebsysRenderer;
+
+fn main() {
+    pretty_env_logger::init();
+    log::info!("Hello!");
+
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example))
+}
+
+static Example: FC<()> = |ctx, props| {
+    let (name, set_name) = use_state(&ctx, || "...?");
+
+    ctx.view(html! {
+        <div>
+            <h1> "Hello, {name}" </h1>
+            <button onclick={move |_| set_name("jack")}> "jack!" </button>
+            <button onclick={move |_| set_name("jill")}> "jill!" </button>
+        </div>
+    })
+};
+
+struct ItemProps {
+    name: String,
+    birthdate: String,
+}
+static Item: FC<ItemProps> = |ctx, ItemProps { name, birthdate }| {
+    ctx.view(html! {
+        <div>
+            <p>"{name}"</p>
+            <p>"{birthdate}"</p>
+        </div>
+    })
+};

+ 2 - 0
packages/web/src/lib.rs

@@ -134,6 +134,8 @@ mod tests {
     use std::env;
 
     use super::*;
+    use dioxus::prelude::bumpalo;
+    use dioxus::prelude::format_args_f;
     use dioxus_core as dioxus;
     use dioxus_core::prelude::html;