From c7c84da8ab4fbab66b1fa9a259da312c3a3417be Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 25 May 2022 08:58:59 -0500 Subject: [PATCH] WIP hot reload --- Cargo.toml | 6 +- packages/core-macro/Cargo.toml | 4 + packages/core-macro/src/lib.rs | 17 +- packages/core-macro/src/rsx/component.rs | 233 --- packages/core-macro/src/rsx/element.rs | 253 --- packages/core-macro/src/rsx/errors.rs | 15 - packages/core-macro/src/rsx/mod.rs | 100 -- packages/core-macro/src/rsx/node.rs | 84 - packages/core-macro/src/rsx/pretty.rs | 1 - packages/core/src/lib.rs | 2 + packages/{core-macro => rsx}/src/ifmt.rs | 6 +- packages/rsx/src/lib.rs | 2 + packages/rsx_interperter/Cargo.toml | 12 + packages/rsx_interperter/src/attributes.rs | 869 +++++++++++ .../rsx_interperter/src/captuered_context.rs | 137 ++ packages/rsx_interperter/src/elements.rs | 1380 +++++++++++++++++ packages/rsx_interperter/src/interperter.rs | 88 ++ packages/rsx_interperter/src/lib.rs | 13 + src/lib.rs | 2 + 19 files changed, 2531 insertions(+), 693 deletions(-) delete mode 100644 packages/core-macro/src/rsx/component.rs delete mode 100644 packages/core-macro/src/rsx/element.rs delete mode 100644 packages/core-macro/src/rsx/errors.rs delete mode 100644 packages/core-macro/src/rsx/mod.rs delete mode 100644 packages/core-macro/src/rsx/node.rs delete mode 100644 packages/core-macro/src/rsx/pretty.rs rename packages/{core-macro => rsx}/src/ifmt.rs (98%) create mode 100644 packages/rsx_interperter/Cargo.toml create mode 100644 packages/rsx_interperter/src/attributes.rs create mode 100644 packages/rsx_interperter/src/captuered_context.rs create mode 100644 packages/rsx_interperter/src/elements.rs create mode 100644 packages/rsx_interperter/src/interperter.rs create mode 100644 packages/rsx_interperter/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index b3f31e516..91955b533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true } dioxus-liveview = { path = "./packages/liveview", optional = true } dioxus-rsx = { path = "./packages/rsx", optional = true } +dioxus-rsx-interperter = { path = "./packages/rsx_interperter", optional = true } dioxus-autofmt = { path = "./packages/autofmt", optional = true } [features] @@ -44,6 +45,7 @@ router = ["dioxus-router"] tui = ["dioxus-tui"] liveview = ["dioxus-liveview"] autofmt = ["dioxus-autofmt"] +hot_reload = ["dioxus-core-macro/hot_reload"] [workspace] members = [ @@ -61,6 +63,7 @@ members = [ "packages/liveview", "packages/rsx", "packages/autofmt", + "packages/rsx_interperter", ] [dev-dependencies] @@ -75,7 +78,8 @@ serde_json = "1.0.79" rand = { version = "0.8.4", features = ["small_rng"] } tokio = { version = "1.16.1", features = ["full"] } reqwest = { version = "0.11.9", features = ["json"] } -dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi", "tui"] } +# dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi", "tui"] } +dioxus = { path = ".", features = ["desktop"] } fern = { version = "0.6.0", features = ["colored"] } criterion = "0.3.5" thiserror = "1.0.30" diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 7aef8f49f..14e1baf3a 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -20,8 +20,12 @@ proc-macro2 = { version = "1.0.6" } quote = "1.0" syn = { version = "1.0.11", features = ["full", "extra-traits"] } dioxus-rsx = { path = "../rsx" } +dioxus-rsx-interperter = { path = "../rsx_interperter", optional = true } # testing [dev-dependencies] rustversion = "1.0" trybuild = "1.0" + +[features] +hot_reload = ["dioxus-rsx-interperter"] \ No newline at end of file diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 06aea9570..f1dd28a60 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -1,8 +1,9 @@ use proc_macro::TokenStream; use quote::ToTokens; +use rsx::{BodyNode, Component, ContentField, ElementAttr, IfmtInput}; +use std::collections::HashMap; use syn::parse_macro_input; -mod ifmt; mod inlineprops; mod props; @@ -11,7 +12,7 @@ use dioxus_rsx as rsx; #[proc_macro] pub fn format_args_f(input: TokenStream) -> TokenStream { - use ifmt::*; + use rsx::*; let item = parse_macro_input!(input as IfmtInput); format_args_f_impl(item) .unwrap_or_else(|err| err.to_compile_error()) @@ -183,7 +184,17 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token pub fn rsx(s: TokenStream) -> TokenStream { match syn::parse::(s) { Err(err) => err.to_compile_error().into(), - Ok(stream) => stream.to_token_stream().into(), + Ok(body) => { + #[cfg(feature = "hot_reload")] + { + use dioxus_rsx_interperter::captuered_context::CapturedContextBuilder; + + let captured = CapturedContextBuilder::from_call_body(body); + captured.to_token_stream().into() + } + #[cfg(not(feature = "hot_reload"))] + body.to_token_stream().into() + } } } diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs deleted file mode 100644 index dfdda74b8..000000000 --- a/packages/core-macro/src/rsx/component.rs +++ /dev/null @@ -1,233 +0,0 @@ -//! 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, Ident, LitStr, Result, Token, -}; - -pub struct Component { - pub name: syn::Path, - pub body: Vec, - pub children: Vec, - pub manual_props: Option, -} - -impl Parse for Component { - fn parse(stream: ParseStream) -> Result { - let name = syn::Path::parse_mod_style(stream)?; - - let content: ParseBuffer; - - // if we see a `{` then we have a block - // else parse as a function-like call - if stream.peek(token::Brace) { - syn::braced!(content in stream); - } else { - syn::parenthesized!(content in stream); - } - - let mut body = Vec::new(); - let mut children = Vec::new(); - let mut manual_props = None; - - while !content.is_empty() { - // if we splat into a component then we're merging properties - if content.peek(Token![..]) { - content.parse::()?; - manual_props = Some(content.parse::()?); - } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) { - body.push(content.parse::()?); - } else { - children.push(content.parse::()?); - } - - if content.peek(Token![,]) { - let _ = content.parse::(); - } - } - - Ok(Self { - name, - body, - children, - manual_props, - }) - } -} - -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 == "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 { - match field.name.to_string().as_str() { - "key" => { - // - has_key = Some(field); - } - _ => toks.append_all(quote! {#field}), - } - } - - if !self.children.is_empty() { - let childs = &self.children; - toks.append_all(quote! { - .children(__cx.create_children([ #( #childs ),* ])) - }); - } - - toks.append_all(quote! { - .build() - }); - toks - } - }; - - let key_token = match has_key { - Some(field) => { - let inners = &field.content; - quote! { Some(format_args_f!(#inners)) } - } - None => quote! { None }, - }; - - let fn_name = self.name.segments.last().unwrap().ident.to_string(); - - tokens.append_all(quote! { - __cx.component( - #name, - #builder, - #key_token, - #fn_name - ) - }) - } -} - -// the struct's fields info -pub struct ComponentField { - name: Ident, - content: ContentField, -} - -enum ContentField { - ManExpr(Expr), - Formatted(LitStr), - OnHandlerRaw(Expr), -} - -impl ToTokens for ContentField { - fn to_tokens(&self, tokens: &mut TokenStream2) { - match self { - ContentField::ManExpr(e) => e.to_tokens(tokens), - ContentField::Formatted(s) => tokens.append_all(quote! { - __cx.raw_text(format_args_f!(#s)).0 - }), - ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { - __cx.event_handler(#e) - }), - } - } -} - -impl Parse for ComponentField { - fn parse(input: ParseStream) -> Result { - let name = Ident::parse_any(input)?; - input.parse::()?; - - if name.to_string().starts_with("on") { - let content = ContentField::OnHandlerRaw(input.parse()?); - return Ok(Self { name, content }); - } - - if name == "key" { - let content = ContentField::ManExpr(input.parse()?); - return Ok(Self { name, content }); - } - - if input.peek(LitStr) && input.peek2(Token![,]) { - let t: LitStr = input.fork().parse()?; - - if is_literal_foramtted(&t) { - let content = ContentField::Formatted(input.parse()?); - return Ok(Self { name, content }); - } - } - - if input.peek(LitStr) && input.peek2(LitStr) { - missing_trailing_comma!(input.span()); - } - - let content = ContentField::ManExpr(input.parse()?); - 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) - }) - } -} - -fn is_literal_foramtted(lit: &LitStr) -> bool { - let s = lit.value(); - let mut chars = s.chars(); - - while let Some(next) = chars.next() { - if next == '{' { - let nen = chars.next(); - if nen != Some('{') { - return true; - } - } - } - - false -} diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs deleted file mode 100644 index 96c7a2efc..000000000 --- a/packages/core-macro/src/rsx/element.rs +++ /dev/null @@ -1,253 +0,0 @@ -use super::*; - -use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{ - parse::{Parse, ParseBuffer, ParseStream}, - Expr, Ident, LitStr, Result, Token, -}; - -// ======================================= -// Parse the VNode::Element type -// ======================================= -pub struct Element { - pub name: Ident, - pub key: Option, - pub attributes: Vec, - pub children: Vec, - pub _is_static: bool, -} - -impl Parse for Element { - fn parse(stream: ParseStream) -> Result { - let el_name = Ident::parse(stream)?; - - // parse the guts - let content: ParseBuffer; - syn::braced!(content in stream); - - let mut attributes: Vec = vec![]; - let mut children: Vec = vec![]; - let mut key = None; - let mut _el_ref = None; - - // parse fields with commas - // break when we don't get this pattern anymore - // start parsing bodynodes - // "def": 456, - // abc: 123, - loop { - // Parse the raw literal fields - if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) { - let name = content.parse::()?; - let ident = name.clone(); - - content.parse::()?; - - if content.peek(LitStr) && content.peek2(Token![,]) { - let value = content.parse::()?; - attributes.push(ElementAttrNamed { - el_name: el_name.clone(), - attr: ElementAttr::CustomAttrText { name, value }, - }); - } else { - let value = content.parse::()?; - - attributes.push(ElementAttrNamed { - el_name: el_name.clone(), - attr: ElementAttr::CustomAttrExpression { name, value }, - }); - } - - if content.is_empty() { - break; - } - - if content.parse::().is_err() { - missing_trailing_comma!(ident); - } - continue; - } - - if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) { - let name = content.parse::()?; - let ident = name.clone(); - - let name_str = name.to_string(); - content.parse::()?; - - if name_str.starts_with("on") { - attributes.push(ElementAttrNamed { - el_name: el_name.clone(), - attr: ElementAttr::EventTokens { - name, - tokens: content.parse()?, - }, - }); - } else { - match name_str.as_str() { - "key" => { - key = Some(content.parse()?); - } - "classes" => todo!("custom class list not supported yet"), - // "namespace" => todo!("custom namespace not supported yet"), - "node_ref" => { - _el_ref = Some(content.parse::()?); - } - _ => { - if content.peek(LitStr) { - attributes.push(ElementAttrNamed { - el_name: el_name.clone(), - attr: ElementAttr::AttrText { - name, - value: content.parse()?, - }, - }); - } else { - attributes.push(ElementAttrNamed { - el_name: el_name.clone(), - attr: ElementAttr::AttrExpression { - name, - value: content.parse()?, - }, - }); - } - } - } - } - - if content.is_empty() { - break; - } - - // todo: add a message saying you need to include commas between fields - if content.parse::().is_err() { - missing_trailing_comma!(ident); - } - continue; - } - - break; - } - - while !content.is_empty() { - if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) { - attr_after_element!(content.span()); - } - - if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) { - attr_after_element!(content.span()); - } - - children.push(content.parse::()?); - // consume comma if it exists - // we don't actually care if there *are* commas after elements/text - if content.peek(Token![,]) { - let _ = content.parse::(); - } - } - - Ok(Self { - key, - name: el_name, - attributes, - children, - _is_static: false, - }) - } -} - -impl ToTokens for Element { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let name = &self.name; - let children = &self.children; - - let key = match &self.key { - Some(ty) => quote! { Some(format_args_f!(#ty)) }, - None => quote! { None }, - }; - - let listeners = self - .attributes - .iter() - .filter(|f| matches!(f.attr, ElementAttr::EventTokens { .. })); - - let attr = self - .attributes - .iter() - .filter(|f| !matches!(f.attr, ElementAttr::EventTokens { .. })); - - tokens.append_all(quote! { - __cx.element( - dioxus_elements::#name, - __cx.bump().alloc([ #(#listeners),* ]), - __cx.bump().alloc([ #(#attr),* ]), - __cx.bump().alloc([ #(#children),* ]), - #key, - ) - }); - } -} - -pub enum ElementAttr { - /// attribute: "valuee {}" - AttrText { name: Ident, value: LitStr }, - - /// attribute: true, - AttrExpression { name: Ident, value: Expr }, - - /// "attribute": "value {}" - CustomAttrText { name: LitStr, value: LitStr }, - - /// "attribute": true, - CustomAttrExpression { name: LitStr, value: Expr }, - - // /// onclick: move |_| {} - // EventClosure { name: Ident, closure: ExprClosure }, - /// onclick: {} - EventTokens { name: Ident, tokens: Expr }, -} - -pub struct ElementAttrNamed { - pub el_name: Ident, - pub attr: ElementAttr, -} - -impl ToTokens for ElementAttrNamed { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let ElementAttrNamed { el_name, attr } = self; - - tokens.append_all(match attr { - ElementAttr::AttrText { name, value } => { - quote! { - dioxus_elements::#el_name.#name(__cx, format_args_f!(#value)) - } - } - ElementAttr::AttrExpression { name, value } => { - quote! { - dioxus_elements::#el_name.#name(__cx, #value) - } - } - ElementAttr::CustomAttrText { name, value } => { - quote! { - __cx.attr( #name, format_args_f!(#value), None, false ) - } - } - ElementAttr::CustomAttrExpression { name, value } => { - quote! { - __cx.attr( #name, format_args_f!(#value), None, false ) - } - } - // ElementAttr::EventClosure { name, closure } => { - // quote! { - // dioxus_elements::on::#name(__cx, #closure) - // } - // } - ElementAttr::EventTokens { name, tokens } => { - quote! { - dioxus_elements::on::#name(__cx, #tokens) - } - } - }); - } -} diff --git a/packages/core-macro/src/rsx/errors.rs b/packages/core-macro/src/rsx/errors.rs deleted file mode 100644 index 27e2c7f98..000000000 --- a/packages/core-macro/src/rsx/errors.rs +++ /dev/null @@ -1,15 +0,0 @@ -macro_rules! missing_trailing_comma { - ($span:expr) => { - proc_macro_error::emit_error!($span, "missing trailing comma") - }; -} - -macro_rules! attr_after_element { - ($span:expr) => { - proc_macro_error::emit_error!( - $span, - "expected element"; - help = "move the attribute above all the children and text elements" - ) - }; -} diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs deleted file mode 100644 index 6b9dfc3dc..000000000 --- a/packages/core-macro/src/rsx/mod.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Parse the root tokens in the rsx!{} macro -//! ========================================= -//! -//! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing. -//! This feature must support: -//! - [x] Optionally rendering if the `in XYZ` pattern is present -//! - [x] Fragments as top-level element (through ambiguous) -//! - [x] Components as top-level element (through ambiguous) -//! - [x] Tags as top-level elements (through ambiguous) -//! - [x] Good errors if parsing fails -//! -//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful. - -#[macro_use] -mod errors; - -mod component; -mod element; -mod node; - -pub mod pretty; - -// Re-export the namespaces into each other -pub use component::*; -pub use element::*; -pub use node::*; - -// imports -use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{ - parse::{Parse, ParseStream}, - Ident, Result, Token, -}; - -pub struct CallBody { - pub custom_context: Option, - pub roots: Vec, -} - -impl Parse for CallBody { - fn parse(input: ParseStream) -> Result { - let custom_context = if input.peek(Ident) && input.peek2(Token![,]) { - let name = input.parse::()?; - input.parse::()?; - - Some(name) - } else { - None - }; - - let mut roots = Vec::new(); - - while !input.is_empty() { - let node = input.parse::()?; - - if input.peek(Token![,]) { - let _ = input.parse::(); - } - - roots.push(node); - } - - Ok(Self { - custom_context, - roots, - }) - } -} - -/// Serialize the same way, regardless of flavor -impl ToTokens for CallBody { - fn to_tokens(&self, out_tokens: &mut TokenStream2) { - let inner = if self.roots.len() == 1 { - let inner = &self.roots[0]; - quote! { #inner } - } else { - let childs = &self.roots; - quote! { __cx.fragment_root([ #(#childs),* ]) } - }; - - match &self.custom_context { - // The `in cx` pattern allows directly rendering - Some(ident) => out_tokens.append_all(quote! { - #ident.render(LazyNodes::new(move |__cx: NodeFactory| -> VNode { - use dioxus_elements::{GlobalAttributes, SvgAttributes}; - #inner - })) - }), - - // Otherwise we just build the LazyNode wrapper - None => out_tokens.append_all(quote! { - LazyNodes::new(move |__cx: NodeFactory| -> VNode { - use dioxus_elements::{GlobalAttributes, SvgAttributes}; - #inner - }) - }), - }; - } -} diff --git a/packages/core-macro/src/rsx/node.rs b/packages/core-macro/src/rsx/node.rs deleted file mode 100644 index d5d701767..000000000 --- a/packages/core-macro/src/rsx/node.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::*; - -use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{ - parse::{Parse, ParseStream}, - token, Expr, LitStr, Result, Token, -}; - -/* -Parse --> div {} --> Component {} --> component() --> "text {with_args}" --> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr -*/ -pub enum BodyNode { - Element(Element), - Component(Component), - Text(LitStr), - RawExpr(Expr), -} - -impl Parse for BodyNode { - fn parse(stream: ParseStream) -> Result { - if stream.peek(LitStr) { - return Ok(BodyNode::Text(stream.parse()?)); - } - - // div {} -> el - // Div {} -> comp - if stream.peek(syn::Ident) && stream.peek2(token::Brace) { - if stream - .fork() - .parse::()? - .to_string() - .chars() - .next() - .unwrap() - .is_ascii_uppercase() - { - return Ok(BodyNode::Component(stream.parse()?)); - } else { - return Ok(BodyNode::Element(stream.parse::()?)); - } - } - - // component() -> comp - // ::component {} -> comp - // ::component () -> comp - if (stream.peek(syn::Ident) && stream.peek2(token::Paren)) - || (stream.peek(Token![::])) - || (stream.peek(Token![:]) && stream.peek2(Token![:])) - { - return Ok(BodyNode::Component(stream.parse::()?)); - } - - // crate::component{} -> comp - // crate::component() -> comp - if let Ok(pat) = stream.fork().parse::() { - if pat.segments.len() > 1 { - return Ok(BodyNode::Component(stream.parse::()?)); - } - } - - Ok(BodyNode::RawExpr(stream.parse::()?)) - } -} - -impl ToTokens for BodyNode { - fn to_tokens(&self, tokens: &mut TokenStream2) { - match &self { - BodyNode::Element(el) => el.to_tokens(tokens), - BodyNode::Component(comp) => comp.to_tokens(tokens), - BodyNode::Text(txt) => tokens.append_all(quote! { - __cx.text(format_args_f!(#txt)) - }), - BodyNode::RawExpr(exp) => tokens.append_all(quote! { - __cx.fragment_from_iter(#exp) - }), - } - } -} diff --git a/packages/core-macro/src/rsx/pretty.rs b/packages/core-macro/src/rsx/pretty.rs deleted file mode 100644 index 26ad54006..000000000 --- a/packages/core-macro/src/rsx/pretty.rs +++ /dev/null @@ -1 +0,0 @@ -//! pretty printer for rsx! diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f31e2e328..eabec8983 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -85,6 +85,8 @@ pub mod prelude { fc_to_builder, Attributes, Component, DioxusElement, Element, EventHandler, Fragment, LazyNodes, NodeFactory, Properties, Scope, ScopeId, ScopeState, VNode, VirtualDom, }; + #[cfg(feature = "hot_reload")] + pub use dioxus_rsx_interperter::captuered_context::{CapturedContext, IfmtArgs}; } pub mod exports { diff --git a/packages/core-macro/src/ifmt.rs b/packages/rsx/src/ifmt.rs similarity index 98% rename from packages/core-macro/src/ifmt.rs rename to packages/rsx/src/ifmt.rs index 912d78f7e..097725043 100644 --- a/packages/core-macro/src/ifmt.rs +++ b/packages/rsx/src/ifmt.rs @@ -140,9 +140,9 @@ pub fn format_args_f_impl(input: IfmtInput) -> Result { #[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)>, + pub format_literal: LitStr, + pub positional_args: Vec, + pub named_args: Vec<(Ident, Expr)>, } impl Parse for IfmtInput { diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 5f01d9306..a5b8143f2 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -16,11 +16,13 @@ mod errors; mod component; mod element; +mod ifmt; mod node; // Re-export the namespaces into each other pub use component::*; pub use element::*; +pub use ifmt::*; pub use node::*; // imports diff --git a/packages/rsx_interperter/Cargo.toml b/packages/rsx_interperter/Cargo.toml new file mode 100644 index 000000000..054e1f563 --- /dev/null +++ b/packages/rsx_interperter/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dioxus-rsx-interperter" +version = "0.1.0" +edition = "2021" + +[dependencies] +syn = "1.0" +quote = "1.0" +dioxus-rsx = { path = "../rsx" } +dioxus-ssr = { path = "../ssr" } +dioxus-core = { path = "../core" } +dioxus-html = { path = "../html" } diff --git a/packages/rsx_interperter/src/attributes.rs b/packages/rsx_interperter/src/attributes.rs new file mode 100644 index 000000000..c0b2c9980 --- /dev/null +++ b/packages/rsx_interperter/src/attributes.rs @@ -0,0 +1,869 @@ +use crate::elements::*; + +// find the mapped attribute name +pub fn attrbute_to_static_str(attr: &str) -> Option<(&'static str, Option<&'static str>)> { + NO_NAMESPACE_ATTRIBUTES + .iter() + .find(|&a| *a == attr) + .map(|a| (*a, None)) + .or_else(|| { + STYLE_ATTRIBUTES + .iter() + .find(|(a, _)| *a == attr) + .map(|(_, b)| (*b, Some("style"))) + }) + .or_else(|| { + MAPPED_ATTRIBUTES + .iter() + .find(|(a, _)| *a == attr) + .map(|(_, b)| (*b, None)) + }) + .or_else(|| { + svg::MAPPED_ATTRIBUTES + .iter() + .find(|(a, _)| *a == attr) + .map(|(_, b)| (*b, None)) + }) + .or_else(|| { + ELEMENTS_WITH_MAPPED_ATTRIBUTES + .iter() + .find_map(|(_, attrs)| { + attrs + .iter() + .find(|(a, _)| *a == attr) + .map(|(_, b)| (*b, None)) + }) + }) + .or_else(|| { + ELEMENTS_WITH_NAMESPACE + .iter() + .find_map(|(_, _, attrs)| attrs.iter().find(|a| **a == attr).map(|a| (*a, None))) + }) + .or_else(|| { + ELEMENTS_WITHOUT_NAMESPACE + .iter() + .find_map(|(_, attrs)| attrs.iter().find(|a| **a == attr).map(|a| (*a, None))) + }) +} + +macro_rules! no_namespace_trait_methods { + ( + $( + $(#[$attr:meta])* + $name:ident; + )* + ) => { + pub const NO_NAMESPACE_ATTRIBUTES: &'static [&'static str] = &[ + $( + stringify!($name), + )* + ]; + }; +} + +macro_rules! style_trait_methods { + ( + $( + $(#[$attr:meta])* + $name:ident: $lit:literal, + )* + ) => { + pub const STYLE_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[ + $( + (stringify!($name), $lit), + )* + ]; + }; +} + +macro_rules! mapped_trait_methods { + ( + $( + $(#[$attr:meta])* + $name:ident: $lit:literal, + )* + ) => { + pub const MAPPED_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[ + $( + (stringify!($name), $lit), + )* + ("prevent_default", "dioxus-prevent-default"), + ]; + }; +} + +no_namespace_trait_methods! { + accesskey; + + /// The HTML class attribute is used to specify a class for an HTML element. + /// + /// ## Details + /// Multiple HTML elements can share the same class. + /// + /// The class global attribute is a space-separated list of the case-sensitive classes of the element. + /// Classes allow CSS and Javascript to select and access specific elements via the class selectors or + /// functions like the DOM method document.getElementsByClassName. + /// + /// ## Example + /// + /// ### HTML: + /// ```html + ///

Above point sounds a bit obvious. Remove/rewrite?

+ /// ``` + /// + /// ### CSS: + /// ```css + /// .note { + /// font-style: italic; + /// font-weight: bold; + /// } + /// + /// .editorial { + /// background: rgb(255, 0, 0, .25); + /// padding: 10px; + /// } + /// ``` + class; + contenteditable; + data; + dir; + draggable; + hidden; + id; + lang; + spellcheck; + style; + tabindex; + title; + translate; + + role; + + /// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting + /// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) + /// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind + /// yourself that it’s dangerous + dangerous_inner_html; +} + +// This macro creates an explicit method call for each of the style attributes. +// +// The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the +// actual name of the attribute generated. +// +// This roughly follows the html spec +style_trait_methods! { + align_content: "align-content", + align_items: "align-items", + align_self: "align-self", + alignment_adjust: "alignment-adjust", + alignment_baseline: "alignment-baseline", + all: "all", + alt: "alt", + animation: "animation", + animation_delay: "animation-delay", + animation_direction: "animation-direction", + animation_duration: "animation-duration", + animation_fill_mode: "animation-fill-mode", + animation_iteration_count: "animation-iteration-count", + animation_name: "animation-name", + animation_play_state: "animation-play-state", + animation_timing_function: "animation-timing-function", + azimuth: "azimuth", + backface_visibility: "backface-visibility", + background: "background", + background_attachment: "background-attachment", + background_clip: "background-clip", + background_color: "background-color", + background_image: "background-image", + background_origin: "background-origin", + background_position: "background-position", + background_repeat: "background-repeat", + background_size: "background-size", + background_blend_mode: "background-blend-mode", + baseline_shift: "baseline-shift", + bleed: "bleed", + bookmark_label: "bookmark-label", + bookmark_level: "bookmark-level", + bookmark_state: "bookmark-state", + border: "border", + border_color: "border-color", + border_style: "border-style", + border_width: "border-width", + border_bottom: "border-bottom", + border_bottom_color: "border-bottom-color", + border_bottom_style: "border-bottom-style", + border_bottom_width: "border-bottom-width", + border_left: "border-left", + border_left_color: "border-left-color", + border_left_style: "border-left-style", + border_left_width: "border-left-width", + border_right: "border-right", + border_right_color: "border-right-color", + border_right_style: "border-right-style", + border_right_width: "border-right-width", + border_top: "border-top", + border_top_color: "border-top-color", + border_top_style: "border-top-style", + border_top_width: "border-top-width", + border_collapse: "border-collapse", + border_image: "border-image", + border_image_outset: "border-image-outset", + border_image_repeat: "border-image-repeat", + border_image_slice: "border-image-slice", + border_image_source: "border-image-source", + border_image_width: "border-image-width", + border_radius: "border-radius", + border_bottom_left_radius: "border-bottom-left-radius", + border_bottom_right_radius: "border-bottom-right-radius", + border_top_left_radius: "border-top-left-radius", + border_top_right_radius: "border-top-right-radius", + border_spacing: "border-spacing", + bottom: "bottom", + box_decoration_break: "box-decoration-break", + box_shadow: "box-shadow", + box_sizing: "box-sizing", + box_snap: "box-snap", + break_after: "break-after", + break_before: "break-before", + break_inside: "break-inside", + buffered_rendering: "buffered-rendering", + caption_side: "caption-side", + clear: "clear", + clear_side: "clear-side", + clip: "clip", + clip_path: "clip-path", + clip_rule: "clip-rule", + color: "color", + color_adjust: "color-adjust", + color_correction: "color-correction", + color_interpolation: "color-interpolation", + color_interpolation_filters: "color-interpolation-filters", + color_profile: "color-profile", + color_rendering: "color-rendering", + column_fill: "column-fill", + column_gap: "column-gap", + column_rule: "column-rule", + column_rule_color: "column-rule-color", + column_rule_style: "column-rule-style", + column_rule_width: "column-rule-width", + column_span: "column-span", + columns: "columns", + column_count: "column-count", + column_width: "column-width", + contain: "contain", + content: "content", + counter_increment: "counter-increment", + counter_reset: "counter-reset", + counter_set: "counter-set", + cue: "cue", + cue_after: "cue-after", + cue_before: "cue-before", + cursor: "cursor", + direction: "direction", + display: "display", + display_inside: "display-inside", + display_outside: "display-outside", + display_extras: "display-extras", + display_box: "display-box", + dominant_baseline: "dominant-baseline", + elevation: "elevation", + empty_cells: "empty-cells", + enable_background: "enable-background", + fill: "fill", + fill_opacity: "fill-opacity", + fill_rule: "fill-rule", + filter: "filter", + float: "float", + float_defer_column: "float-defer-column", + float_defer_page: "float-defer-page", + float_offset: "float-offset", + float_wrap: "float-wrap", + flow_into: "flow-into", + flow_from: "flow-from", + flex: "flex", + flex_basis: "flex-basis", + flex_grow: "flex-grow", + flex_shrink: "flex-shrink", + flex_flow: "flex-flow", + flex_direction: "flex-direction", + flex_wrap: "flex-wrap", + flood_color: "flood-color", + flood_opacity: "flood-opacity", + font: "font", + font_family: "font-family", + font_size: "font-size", + font_stretch: "font-stretch", + font_style: "font-style", + font_weight: "font-weight", + font_feature_settings: "font-feature-settings", + font_kerning: "font-kerning", + font_language_override: "font-language-override", + font_size_adjust: "font-size-adjust", + font_synthesis: "font-synthesis", + font_variant: "font-variant", + font_variant_alternates: "font-variant-alternates", + font_variant_caps: "font-variant-caps", + font_variant_east_asian: "font-variant-east-asian", + font_variant_ligatures: "font-variant-ligatures", + font_variant_numeric: "font-variant-numeric", + font_variant_position: "font-variant-position", + footnote_policy: "footnote-policy", + glyph_orientation_horizontal: "glyph-orientation-horizontal", + glyph_orientation_vertical: "glyph-orientation-vertical", + grid: "grid", + grid_auto_flow: "grid-auto-flow", + grid_auto_columns: "grid-auto-columns", + grid_auto_rows: "grid-auto-rows", + grid_template: "grid-template", + grid_template_areas: "grid-template-areas", + grid_template_columns: "grid-template-columns", + grid_template_rows: "grid-template-rows", + grid_area: "grid-area", + grid_column: "grid-column", + grid_column_start: "grid-column-start", + grid_column_end: "grid-column-end", + grid_row: "grid-row", + grid_row_start: "grid-row-start", + grid_row_end: "grid-row-end", + hanging_punctuation: "hanging-punctuation", + height: "height", + hyphenate_character: "hyphenate-character", + hyphenate_limit_chars: "hyphenate-limit-chars", + hyphenate_limit_last: "hyphenate-limit-last", + hyphenate_limit_lines: "hyphenate-limit-lines", + hyphenate_limit_zone: "hyphenate-limit-zone", + hyphens: "hyphens", + icon: "icon", + image_orientation: "image-orientation", + image_resolution: "image-resolution", + image_rendering: "image-rendering", + ime: "ime", + ime_align: "ime-align", + ime_mode: "ime-mode", + ime_offset: "ime-offset", + ime_width: "ime-width", + initial_letters: "initial-letters", + inline_box_align: "inline-box-align", + isolation: "isolation", + justify_content: "justify-content", + justify_items: "justify-items", + justify_self: "justify-self", + kerning: "kerning", + left: "left", + letter_spacing: "letter-spacing", + lighting_color: "lighting-color", + line_box_contain: "line-box-contain", + line_break: "line-break", + line_grid: "line-grid", + line_height: "line-height", + line_slack: "line-slack", + line_snap: "line-snap", + list_style: "list-style", + list_style_image: "list-style-image", + list_style_position: "list-style-position", + list_style_type: "list-style-type", + margin: "margin", + margin_bottom: "margin-bottom", + margin_left: "margin-left", + margin_right: "margin-right", + margin_top: "margin-top", + marker: "marker", + marker_end: "marker-end", + marker_mid: "marker-mid", + marker_pattern: "marker-pattern", + marker_segment: "marker-segment", + marker_start: "marker-start", + marker_knockout_left: "marker-knockout-left", + marker_knockout_right: "marker-knockout-right", + marker_side: "marker-side", + marks: "marks", + marquee_direction: "marquee-direction", + marquee_play_count: "marquee-play-count", + marquee_speed: "marquee-speed", + marquee_style: "marquee-style", + mask: "mask", + mask_image: "mask-image", + mask_repeat: "mask-repeat", + mask_position: "mask-position", + mask_clip: "mask-clip", + mask_origin: "mask-origin", + mask_size: "mask-size", + mask_box: "mask-box", + mask_box_outset: "mask-box-outset", + mask_box_repeat: "mask-box-repeat", + mask_box_slice: "mask-box-slice", + mask_box_source: "mask-box-source", + mask_box_width: "mask-box-width", + mask_type: "mask-type", + max_height: "max-height", + max_lines: "max-lines", + max_width: "max-width", + min_height: "min-height", + min_width: "min-width", + mix_blend_mode: "mix-blend-mode", + nav_down: "nav-down", + nav_index: "nav-index", + nav_left: "nav-left", + nav_right: "nav-right", + nav_up: "nav-up", + object_fit: "object-fit", + object_position: "object-position", + offset_after: "offset-after", + offset_before: "offset-before", + offset_end: "offset-end", + offset_start: "offset-start", + opacity: "opacity", + order: "order", + orphans: "orphans", + outline: "outline", + outline_color: "outline-color", + outline_style: "outline-style", + outline_width: "outline-width", + outline_offset: "outline-offset", + overflow: "overflow", + overflow_x: "overflow-x", + overflow_y: "overflow-y", + overflow_style: "overflow-style", + overflow_wrap: "overflow-wrap", + padding: "padding", + padding_bottom: "padding-bottom", + padding_left: "padding-left", + padding_right: "padding-right", + padding_top: "padding-top", + page: "page", + page_break_after: "page-break-after", + page_break_before: "page-break-before", + page_break_inside: "page-break-inside", + paint_order: "paint-order", + pause: "pause", + pause_after: "pause-after", + pause_before: "pause-before", + perspective: "perspective", + perspective_origin: "perspective-origin", + pitch: "pitch", + pitch_range: "pitch-range", + play_during: "play-during", + pointer_events: "pointer-events", + position: "position", + quotes: "quotes", + region_fragment: "region-fragment", + resize: "resize", + rest: "rest", + rest_after: "rest-after", + rest_before: "rest-before", + richness: "richness", + right: "right", + ruby_align: "ruby-align", + ruby_merge: "ruby-merge", + ruby_position: "ruby-position", + scroll_behavior: "scroll-behavior", + scroll_snap_coordinate: "scroll-snap-coordinate", + scroll_snap_destination: "scroll-snap-destination", + scroll_snap_points_x: "scroll-snap-points-x", + scroll_snap_points_y: "scroll-snap-points-y", + scroll_snap_type: "scroll-snap-type", + shape_image_threshold: "shape-image-threshold", + shape_inside: "shape-inside", + shape_margin: "shape-margin", + shape_outside: "shape-outside", + shape_padding: "shape-padding", + shape_rendering: "shape-rendering", + size: "size", + speak: "speak", + speak_as: "speak-as", + speak_header: "speak-header", + speak_numeral: "speak-numeral", + speak_punctuation: "speak-punctuation", + speech_rate: "speech-rate", + stop_color: "stop-color", + stop_opacity: "stop-opacity", + stress: "stress", + string_set: "string-set", + stroke: "stroke", + stroke_dasharray: "stroke-dasharray", + stroke_dashoffset: "stroke-dashoffset", + stroke_linecap: "stroke-linecap", + stroke_linejoin: "stroke-linejoin", + stroke_miterlimit: "stroke-miterlimit", + stroke_opacity: "stroke-opacity", + stroke_width: "stroke-width", + tab_size: "tab-size", + table_layout: "table-layout", + text_align: "text-align", + text_align_all: "text-align-all", + text_align_last: "text-align-last", + text_anchor: "text-anchor", + text_combine_upright: "text-combine-upright", + text_decoration: "text-decoration", + text_decoration_color: "text-decoration-color", + text_decoration_line: "text-decoration-line", + text_decoration_style: "text-decoration-style", + text_decoration_skip: "text-decoration-skip", + text_emphasis: "text-emphasis", + text_emphasis_color: "text-emphasis-color", + text_emphasis_style: "text-emphasis-style", + text_emphasis_position: "text-emphasis-position", + text_emphasis_skip: "text-emphasis-skip", + text_height: "text-height", + text_indent: "text-indent", + text_justify: "text-justify", + text_orientation: "text-orientation", + text_overflow: "text-overflow", + text_rendering: "text-rendering", + text_shadow: "text-shadow", + text_size_adjust: "text-size-adjust", + text_space_collapse: "text-space-collapse", + text_spacing: "text-spacing", + text_transform: "text-transform", + text_underline_position: "text-underline-position", + text_wrap: "text-wrap", + top: "top", + touch_action: "touch-action", + transform: "transform", + transform_box: "transform-box", + transform_origin: "transform-origin", + transform_style: "transform-style", + transition: "transition", + transition_delay: "transition-delay", + transition_duration: "transition-duration", + transition_property: "transition-property", + unicode_bidi: "unicode-bidi", + vector_effect: "vector-effect", + vertical_align: "vertical-align", + visibility: "visibility", + voice_balance: "voice-balance", + voice_duration: "voice-duration", + voice_family: "voice-family", + voice_pitch: "voice-pitch", + voice_range: "voice-range", + voice_rate: "voice-rate", + voice_stress: "voice-stress", + voice_volumn: "voice-volumn", + volume: "volume", + white_space: "white-space", + widows: "widows", + width: "width", + will_change: "will-change", + word_break: "word-break", + word_spacing: "word-spacing", + word_wrap: "word-wrap", + wrap_flow: "wrap-flow", + wrap_through: "wrap-through", + writing_mode: "writing-mode", + gap: "gap", + list_styler_type: "list-style-type", + row_gap: "row-gap", + transition_timing_function: "transition-timing-function", + user_select: "user-select", + webkit_user_select: "-webkit-user-select", + z_index : "z-index", +} +mapped_trait_methods! { + aria_current: "aria-current", + aria_details: "aria-details", + aria_disabled: "aria-disabled", + aria_hidden: "aria-hidden", + aria_invalid: "aria-invalid", + aria_keyshortcuts: "aria-keyshortcuts", + aria_label: "aria-label", + aria_roledescription: "aria-roledescription", + // Widget Attributes + aria_autocomplete: "aria-autocomplete", + aria_checked: "aria-checked", + aria_expanded: "aria-expanded", + aria_haspopup: "aria-haspopup", + aria_level: "aria-level", + aria_modal: "aria-modal", + aria_multiline: "aria-multiline", + aria_multiselectable: "aria-multiselectable", + aria_orientation: "aria-orientation", + aria_placeholder: "aria-placeholder", + aria_pressed: "aria-pressed", + aria_readonly: "aria-readonly", + aria_required: "aria-required", + aria_selected: "aria-selected", + aria_sort: "aria-sort", + aria_valuemax: "aria-valuemax", + aria_valuemin: "aria-valuemin", + aria_valuenow: "aria-valuenow", + aria_valuetext: "aria-valuetext", + // Live Region Attributes + aria_atomic: "aria-atomic", + aria_busy: "aria-busy", + aria_live: "aria-live", + aria_relevant: "aria-relevant", + + aria_dropeffect: "aria-dropeffect", + aria_grabbed: "aria-grabbed", + // Relationship Attributes + aria_activedescendant: "aria-activedescendant", + aria_colcount: "aria-colcount", + aria_colindex: "aria-colindex", + aria_colspan: "aria-colspan", + aria_controls: "aria-controls", + aria_describedby: "aria-describedby", + aria_errormessage: "aria-errormessage", + aria_flowto: "aria-flowto", + aria_labelledby: "aria-labelledby", + aria_owns: "aria-owns", + aria_posinset: "aria-posinset", + aria_rowcount: "aria-rowcount", + aria_rowindex: "aria-rowindex", + aria_rowspan: "aria-rowspan", + aria_setsize: "aria-setsize", +} + +pub mod svg { + mapped_trait_methods! { + accent_height: "accent-height", + accumulate: "accumulate", + additive: "additive", + alignment_baseline: "alignment-baseline", + alphabetic: "alphabetic", + amplitude: "amplitude", + arabic_form: "arabic-form", + ascent: "ascent", + attributeName: "attributeName", + attributeType: "attributeType", + azimuth: "azimuth", + baseFrequency: "baseFrequency", + baseline_shift: "baseline-shift", + baseProfile: "baseProfile", + bbox: "bbox", + begin: "begin", + bias: "bias", + by: "by", + calcMode: "calcMode", + cap_height: "cap-height", + class: "class", + clip: "clip", + clipPathUnits: "clipPathUnits", + clip_path: "clip-path", + clip_rule: "clip-rule", + color: "color", + color_interpolation: "color-interpolation", + color_interpolation_filters: "color-interpolation-filters", + color_profile: "color-profile", + color_rendering: "color-rendering", + contentScriptType: "contentScriptType", + contentStyleType: "contentStyleType", + crossorigin: "crossorigin", + cursor: "cursor", + cx: "cx", + cy: "cy", + d: "d", + decelerate: "decelerate", + descent: "descent", + diffuseConstant: "diffuseConstant", + direction: "direction", + display: "display", + divisor: "divisor", + dominant_baseline: "dominant-baseline", + dur: "dur", + dx: "dx", + dy: "dy", + edgeMode: "edgeMode", + elevation: "elevation", + enable_background: "enable-background", + end: "end", + exponent: "exponent", + fill: "fill", + fill_opacity: "fill-opacity", + fill_rule: "fill-rule", + filter: "filter", + filterRes: "filterRes", + filterUnits: "filterUnits", + flood_color: "flood-color", + flood_opacity: "flood-opacity", + font_family: "font-family", + font_size: "font-size", + font_size_adjust: "font-size-adjust", + font_stretch: "font-stretch", + font_style: "font-style", + font_variant: "font-variant", + font_weight: "font-weight", + format: "format", + from: "from", + fr: "fr", + fx: "fx", + fy: "fy", + g1: "g1", + g2: "g2", + glyph_name: "glyph-name", + glyph_orientation_horizontal: "glyph-orientation-horizontal", + glyph_orientation_vertical: "glyph-orientation-vertical", + glyphRef: "glyphRef", + gradientTransform: "gradientTransform", + gradientUnits: "gradientUnits", + hanging: "hanging", + height: "height", + href: "href", + hreflang: "hreflang", + horiz_adv_x: "horiz-adv-x", + horiz_origin_x: "horiz-origin-x", + id: "id", + ideographic: "ideographic", + image_rendering: "image-rendering", + _in: "_in", + in2: "in2", + intercept: "intercept", + k: "k", + k1: "k1", + k2: "k2", + k3: "k3", + k4: "k4", + kernelMatrix: "kernelMatrix", + kernelUnitLength: "kernelUnitLength", + kerning: "kerning", + keyPoints: "keyPoints", + keySplines: "keySplines", + keyTimes: "keyTimes", + lang: "lang", + lengthAdjust: "lengthAdjust", + letter_spacing: "letter-spacing", + lighting_color: "lighting-color", + limitingConeAngle: "limitingConeAngle", + local: "local", + marker_end: "marker-end", + marker_mid: "marker-mid", + marker_start: "marker_start", + markerHeight: "markerHeight", + markerUnits: "markerUnits", + markerWidth: "markerWidth", + mask: "mask", + maskContentUnits: "maskContentUnits", + maskUnits: "maskUnits", + mathematical: "mathematical", + max: "max", + media: "media", + method: "method", + min: "min", + mode: "mode", + name: "name", + numOctaves: "numOctaves", + offset: "offset", + opacity: "opacity", + operator: "operator", + order: "order", + orient: "orient", + orientation: "orientation", + origin: "origin", + overflow: "overflow", + overline_position: "overline-position", + overline_thickness: "overline-thickness", + panose_1: "panose-1", + paint_order: "paint-order", + path: "path", + pathLength: "pathLength", + patternContentUnits: "patternContentUnits", + patternTransform: "patternTransform", + patternUnits: "patternUnits", + ping: "ping", + pointer_events: "pointer-events", + points: "points", + pointsAtX: "pointsAtX", + pointsAtY: "pointsAtY", + pointsAtZ: "pointsAtZ", + preserveAlpha: "preserveAlpha", + preserveAspectRatio: "preserveAspectRatio", + primitiveUnits: "primitiveUnits", + r: "r", + radius: "radius", + referrerPolicy: "referrerPolicy", + refX: "refX", + refY: "refY", + rel: "rel", + rendering_intent: "rendering-intent", + repeatCount: "repeatCount", + repeatDur: "repeatDur", + requiredExtensions: "requiredExtensions", + requiredFeatures: "requiredFeatures", + restart: "restart", + result: "result", + role: "role", + rotate: "rotate", + rx: "rx", + ry: "ry", + scale: "scale", + seed: "seed", + shape_rendering: "shape-rendering", + slope: "slope", + spacing: "spacing", + specularConstant: "specularConstant", + specularExponent: "specularExponent", + speed: "speed", + spreadMethod: "spreadMethod", + startOffset: "startOffset", + stdDeviation: "stdDeviation", + stemh: "stemh", + stemv: "stemv", + stitchTiles: "stitchTiles", + stop_color: "stop_color", + stop_opacity: "stop_opacity", + strikethrough_position: "strikethrough-position", + strikethrough_thickness: "strikethrough-thickness", + string: "string", + stroke: "stroke", + stroke_dasharray: "stroke-dasharray", + stroke_dashoffset: "stroke-dashoffset", + stroke_linecap: "stroke-linecap", + stroke_linejoin: "stroke-linejoin", + stroke_miterlimit: "stroke-miterlimit", + stroke_opacity: "stroke-opacity", + stroke_width: "stroke-width", + style: "style", + surfaceScale: "surfaceScale", + systemLanguage: "systemLanguage", + tabindex: "tabindex", + tableValues: "tableValues", + target: "target", + targetX: "targetX", + targetY: "targetY", + text_anchor: "text-anchor", + text_decoration: "text-decoration", + text_rendering: "text-rendering", + textLength: "textLength", + to: "to", + transform: "transform", + transform_origin: "transform-origin", + r#type: "_type", + u1: "u1", + u2: "u2", + underline_position: "underline-position", + underline_thickness: "underline-thickness", + unicode: "unicode", + unicode_bidi: "unicode-bidi", + unicode_range: "unicode-range", + units_per_em: "units-per-em", + v_alphabetic: "v-alphabetic", + v_hanging: "v-hanging", + v_ideographic: "v-ideographic", + v_mathematical: "v-mathematical", + values: "values", + vector_effect: "vector-effect", + version: "version", + vert_adv_y: "vert-adv-y", + vert_origin_x: "vert-origin-x", + vert_origin_y: "vert-origin-y", + view_box: "viewBox", + view_target: "viewTarget", + visibility: "visibility", + width: "width", + widths: "widths", + word_spacing: "word-spacing", + writing_mode: "writing-mode", + x: "x", + x_height: "x-height", + x1: "x1", + x2: "x2", + xmlns: "xmlns", + x_channel_selector: "xChannelSelector", + y: "y", + y1: "y1", + y2: "y2", + y_channel_selector: "yChannelSelector", + z: "z", + zoomAndPan: "zoomAndPan", + } +} diff --git a/packages/rsx_interperter/src/captuered_context.rs b/packages/rsx_interperter/src/captuered_context.rs new file mode 100644 index 000000000..f3df4f341 --- /dev/null +++ b/packages/rsx_interperter/src/captuered_context.rs @@ -0,0 +1,137 @@ +use dioxus_core::VNode; +use dioxus_rsx::{BodyNode, CallBody, Component, ElementAttr, IfmtInput}; +use quote::{quote, ToTokens, TokenStreamExt}; +use std::collections::HashMap; +use syn::Expr; + +#[derive(Default)] +pub struct CapturedContextBuilder { + attributes: HashMap, + text: Vec, + components: Vec, + iterators: Vec, +} + +impl CapturedContextBuilder { + pub fn extend(&mut self, other: CapturedContextBuilder) { + self.attributes.extend(other.attributes); + self.text.extend(other.text); + self.components.extend(other.components); + } + + pub fn from_call_body(body: CallBody) -> Self { + let mut new = Self::default(); + for node in body.roots { + new.extend(Self::find_captured(node)); + } + new + } + + fn find_captured(node: BodyNode) -> Self { + let mut captured = CapturedContextBuilder::default(); + match node { + BodyNode::Element(el) => { + for attr in el.attributes { + let (name, value_tokens) = match attr.attr { + ElementAttr::AttrText { name, value } => { + (name.to_string(), value.to_token_stream()) + } + ElementAttr::AttrExpression { name, value } => { + (name.to_string(), value.to_token_stream()) + } + ElementAttr::CustomAttrText { name, value } => { + (name.value(), value.to_token_stream()) + } + ElementAttr::CustomAttrExpression { name, value } => { + (name.value(), value.to_token_stream()) + } + _ => continue, + }; + let formated: IfmtInput = syn::parse2(value_tokens).unwrap(); + captured.attributes.insert(name, formated); + } + for child in el.children { + captured.extend(Self::find_captured(child)); + } + } + BodyNode::Component(comp) => { + let fn_name = comp.name.segments.last().unwrap().ident.to_string(); + captured.components.push(comp); + } + BodyNode::Text(t) => { + let tokens = t.to_token_stream(); + let formated: IfmtInput = syn::parse2(tokens).unwrap(); + captured.text.push(formated); + } + BodyNode::RawExpr(expr) => captured.iterators.push(expr), + BodyNode::Meta(_) => (), + } + captured + } +} + +impl ToTokens for CapturedContextBuilder { + fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) { + let CapturedContextBuilder { + attributes, + text, + components, + iterators, + } = self; + let captured: Vec<_> = attributes + .iter() + .map(|(_, fmt)| fmt.named_args.iter()) + .chain(text.iter().map(|fmt| fmt.named_args.iter())) + .flatten() + .collect(); + let captured_names = captured.iter().map(|(n, _)| n); + let captured_expr = captured.iter().map(|(_, e)| e); + tokens.append_all(quote! { + CapturedContext { + captured: IfmtArgs{ + named_args: &'static [#((#captured_names, #captured_expr)),*] + }, + components: vec![#(#components),*], + iterators: vec![#(#iterators),*], + } + }) + } +} + +struct CapturedComponentBuilder { + name: syn::Path, + function: String, +} + +pub struct CapturedContext<'a> { + // map of the attribute name to the formated value + captured: IfmtArgs, + // the only thing we can update in component is the children + components: Vec>, + // we can't reasonably interpert iterators, so they are staticly inserted + iterators: Vec>, +} + +pub struct IfmtArgs { + // live reload only supports named arguments + pub named_args: &'static [(&'static str, String)], +} + +enum IfmtSegment<'a> { + Static(&'a str), + Dynamic(&'a str), +} + +enum RsxNode<'a> { + Element { + name: String, + attributes: Vec<(String, IfmtSegment<'a>)>, + children: Vec>, + }, + Text { + text: Vec>, + }, + Component { + children: Vec>, + }, +} diff --git a/packages/rsx_interperter/src/elements.rs b/packages/rsx_interperter/src/elements.rs new file mode 100644 index 000000000..032c8f98e --- /dev/null +++ b/packages/rsx_interperter/src/elements.rs @@ -0,0 +1,1380 @@ +// find the mapped attribute name +pub fn element_to_static_str(element: &str) -> Option<(&'static str, Option<&'static str>)> { + ELEMENTS_WITH_MAPPED_ATTRIBUTES + .iter() + .find(|(el, _)| *el == element) + .map(|(el, _)| (*el, None)) + .or_else(|| { + ELEMENTS_WITH_NAMESPACE + .iter() + .find(|(el, _, _)| *el == element) + .map(|(el, ns, _)| (*el, Some(*ns))) + }) + .or_else(|| { + ELEMENTS_WITHOUT_NAMESPACE + .iter() + .find(|(el, _)| *el == element) + .map(|(el, _)| (*el, None)) + }) +} + +macro_rules! builder_constructors { + ( + $( + $(#[$attr:meta])* + $name:ident { + $( + $(#[$attr_method:meta])* + $fil:ident: $vil:ident, + )* + }; + )* + ) => { + pub const ELEMENTS_WITHOUT_NAMESPACE: &'static [(&'static str, &'static [&'static str])] = &[ + $( + ( + stringify!($name), + &[ + $( + stringify!($fil), + )* + ] + ), + )* + ]; + }; + + ( $( + $(#[$attr:meta])* + $name:ident <> $namespace:tt { + $($fil:ident: $vil:ident,)* + }; + )* ) => { + pub const ELEMENTS_WITH_NAMESPACE: &'static [(&'static str, &'static str, &'static [&'static str])] = &[ + $( + ( + stringify!($name), + stringify!($namespace), + &[ + $( + stringify!($fil), + )* + ] + ), + )* + ]; + }; +} +pub const ELEMENTS_WITH_MAPPED_ATTRIBUTES: &'static [( + &'static str, + &'static [(&'static str, &'static str)], +)] = &[ + ("script", &[("r#type", "type"), ("r#script", "script")]), + ("button", &[("r#type", "type")]), + ("select", &[("value", "value")]), + ("option", &[("selected", "selected")]), + ("textarea", &[("value", "value")]), + ("label", &[("r#for", "for")]), + ("input", &[("r#type", "type"), ("value", "value")]), +]; + +// Organized in the same order as +// https://developer.mozilla.org/en-US/docs/Web/HTML/Element +// +// Does not include obsolete elements. +// +// This namespace represents a collection of modern HTML-5 compatiable elements. +// +// This list does not include obsolete, deprecated, experimental, or poorly supported elements. +builder_constructors! { + // Document metadata + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) + /// element. + /// + base { + href: Uri, + target: Target, + }; + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) + /// element. + head {}; + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) + /// element. + link { + // as: Mime, + crossorigin: CrossOrigin, + href: Uri, + hreflang: LanguageTag, + media: String, // FIXME media query + rel: LinkType, + sizes: String, // FIXME + title: String, // FIXME + r#type: Mime, + integrity: String, + }; + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) + /// element. + meta { + charset: String, // FIXME IANA standard names + content: String, + http_equiv: HTTPEquiv, + name: Metadata, + }; + + /// Build a + /// [`