From e4b1f6ea0d0db707cf757dabf8635e9fc91a3e0f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 26 Feb 2021 20:42:55 -0500 Subject: [PATCH] Feat: custom format_args for inlining variables into html templates --- packages/core-macro/Cargo.toml | 1 + packages/core-macro/src/fc.rs | 322 ++++++++++++++++++++++++++++ packages/core-macro/src/ifmt.rs | 271 ++++++++++++++++++++++++ packages/core-macro/src/lib.rs | 327 ++--------------------------- packages/core/Cargo.toml | 1 + packages/core/examples/fmter.rs | 67 ++++++ packages/core/examples/listener.rs | 23 +- packages/core/src/lib.rs | 119 ++++++++++- packages/core/src/nodebuilder.rs | 25 ++- packages/core/src/scope.rs | 3 + packages/html-macro-2/src/lib.rs | 20 +- packages/web/examples/jackjill.rs | 38 ++++ packages/web/src/lib.rs | 2 + 13 files changed, 886 insertions(+), 333 deletions(-) create mode 100644 packages/core-macro/src/fc.rs create mode 100644 packages/core-macro/src/ifmt.rs create mode 100644 packages/core/examples/fmter.rs create mode 100644 packages/web/examples/jackjill.rs diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 77a4dd333..41db6824f 100644 --- a/packages/core-macro/Cargo.toml +++ b/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"] } diff --git a/packages/core-macro/src/fc.rs b/packages/core-macro/src/fc.rs new file mode 100644 index 000000000..c7b87ce62 --- /dev/null +++ b/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 { + // 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, + + // The user's props type + props_type: Box, + + arg: FnArg, + vis: Visibility, + attrs: Vec, + name: Ident, + return_type: Box, +} + +impl Parse for FunctionComponent { + fn parse(input: ParseStream) -> syn::Result { + 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::>(); + + // 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 { + 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, + } = ⁢ + 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> { + 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 { + 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> { + 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 { + if input.is_empty() { + return Err(input.error("expected identifier for the component")); + } + + let component_name = input.parse()?; + + Ok(Self { component_name }) + } +} diff --git a/packages/core-macro/src/ifmt.rs b/packages/core-macro/src/ifmt.rs new file mode 100644 index 000000000..d0e341eb3 --- /dev/null +++ b/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 = { + impl Parse for Segment { + fn parse(input: ParseStream<'_>) -> Result { + 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::::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 = 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, + named_args: Vec<(Ident, Expr)>, +} + +impl Parse for IfmtInput { + fn parse(input: ParseStream) -> Result { + let format_literal = input.parse()?; + let mut positional_args = vec![]; + loop { + if input.parse::>()?.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 + 'a +where + Drop: FnOnce(T), +{ + use ::core::mem::ManuallyDrop; + struct Ret(ManuallyDrop, ManuallyDrop) + where + Drop: FnOnce(T); + impl ::core::ops::Drop for Ret + 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 ::core::ops::Deref for Ret + where + Drop: FnOnce(T), + { + type Target = T; + #[inline] + fn deref(self: &'_ Self) -> &'_ Self::Target { + &self.0 + } + } + impl ::core::ops::DerefMut for Ret + where + Drop: FnOnce(T), + { + #[inline] + fn deref_mut(self: &'_ mut Self) -> &'_ mut Self::Target { + &mut self.0 + } + } + Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop)) +} diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 6de70a470..b9eddc1fa 100644 --- a/packages/core-macro/src/lib.rs +++ b/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() + + // function_component_impl(attr, item) + // let attr = parse_macro_input!(attr as FunctionComponentName); } -fn function_component_impl( - // name: FunctionComponentName, - component: FunctionComponent, -) -> syn::Result { - // let FunctionComponentName { component_name } = name; +#[proc_macro] +pub fn format_args_f(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + use ifmt::*; - let FunctionComponent { - block, - props_type, - arg, - vis, - attrs, - name: function_name, - return_type, - } = component; + let item = parse_macro_input!(input as IfmtInput); - // if function_name == component_name { - // return Err(syn::Error::new_spanned( - // component_name, - // "the component must not have the same name as the function", - // )); - // } + // #[allow(unused)] + // const FUNCTION_NAME: &str = "format_args_f"; - let quoted = quote! { - #[doc(hidden)] - #[allow(non_camel_case_types)] - mod __component_blah { - use super::*; + // debug_input!(&input); - #[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, - - // The user's props type - props_type: Box, - - arg: FnArg, - vis: Visibility, - attrs: Vec, - name: Ident, - return_type: Box, -} - -impl Parse for FunctionComponent { - fn parse(input: ParseStream) -> syn::Result { - 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::>(); - - // 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 { - 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, - } = ⁢ - 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> { - 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 { - 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> { - 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", - )); - } -} - -fn collect_inline_args() {} - -/// The named specified in the macro usage. -struct FunctionComponentName { - component_name: Ident, -} - -impl Parse for FunctionComponentName { - fn parse(input: ParseStream) -> syn::Result { - if input.is_empty() { - return Err(input.error("expected identifier for the component")); - } - - let component_name = input.parse()?; - - Ok(Self { component_name }) - } + ifmt::format_args_f_impl(item) + // .unwrap_or_else(|err| err.to_compile_error()) + // .into() } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index dbbd5818a..48e74264d 100644 --- a/packages/core/Cargo.toml +++ b/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"] } diff --git a/packages/core/examples/fmter.rs b/packages/core/examples/fmter.rs new file mode 100644 index 000000000..22c1684c2 --- /dev/null +++ b/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 = ≎ + 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 = ≎ +// 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 +// }; +// } diff --git a/packages/core/examples/listener.rs b/packages/core/examples/listener.rs index e36bc0c6f..bf7d3ccc3 100644 --- a/packages/core/examples/listener.rs +++ b/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! {
- - - -
-

"Value is: {val1}"

-
+

"Hello, {name}"

+ +
}) }; diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 90af768dc..83e87f656 100644 --- a/packages/core/src/lib.rs +++ b/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); +} diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 63ff7446b..8ca9686bd 100644 --- a/packages/core/src/nodebuilder.rs +++ b/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(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 diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 0b35e3535..6749316bb 100644 --- a/packages/core/src/scope.rs +++ b/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! { diff --git a/packages/html-macro-2/src/lib.rs b/packages/html-macro-2/src/lib.rs index e607cbb7e..e7764fddf 100644 --- a/packages/html-macro-2/src/lib.rs +++ b/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, @@ -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); @@ -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) + } }); } } diff --git a/packages/web/examples/jackjill.rs b/packages/web/examples/jackjill.rs new file mode 100644 index 000000000..d14680c91 --- /dev/null +++ b/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! { +
+

"Hello, {name}"

+ + +
+ }) +}; + +struct ItemProps { + name: String, + birthdate: String, +} +static Item: FC = |ctx, ItemProps { name, birthdate }| { + ctx.view(html! { +
+

"{name}"

+

"{birthdate}"

+
+ }) +}; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 454d10c26..7c4399788 100644 --- a/packages/web/src/lib.rs +++ b/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;