Explorar o código

handle formatting options

Evan Almloff %!s(int64=3) %!d(string=hai) anos
pai
achega
94448ea4aa

+ 28 - 30
examples/hot_reload.rs

@@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element {
             },
 
             p {
-                "High-Five counter: {count}",
+                "High-Five counter: {count.to_string():?}",
             }
 
             div {
@@ -43,33 +43,31 @@ fn app(cx: Scope) -> Element {
 
     cx.render(rsx! {
         div {
-             display: "flex",
-             flex_direction: "row",
-             width: "100%",
-             height: "50%",
-             Editable{
-                current_code: submitted_rsx_code.get().clone(),
-             },
-
-             textarea {
-                 width: "90%",
-                 value:
-                    rsx_code
-                 ,
-                 oninput: move |evt| {
-                     rsx_code.set(evt.value.clone());
-                 },
-             }
-
-             button {
-                 height: "100%",
-                 width: "10%",
-                 onclick: move |_|{
-                         submitted_rsx_code.set(Some(rsx_code.get().clone()));
-                 },
-                 "submit"
-             }
-         }
+            display: "flex",
+            flex_direction: "row",
+            width: "100%",
+            height: "50%",
+            Editable{
+               current_code: submitted_rsx_code.get().clone(),
+            },
+
+            textarea {
+                width: "90%",
+                value: rsx_code,
+                oninput: move |evt| {
+                    rsx_code.set(evt.value.clone());
+                },
+            }
+
+            button {
+                height: "100%",
+                width: "10%",
+                onclick: move |_|{
+                   submitted_rsx_code.set(Some(rsx_code.get().clone()));
+                },
+                "submit"
+            }
+        }
     })
 }
 
@@ -86,7 +84,7 @@ fn Editable(cx: Scope<EditableProps>) -> Element {
         rsx_index.insert(
             CodeLocation {
                 file: r"examples\hot_reload.rs".to_string(),
-                line: 95,
+                line: 93,
                 column: 15,
             },
             code.clone(),
@@ -101,7 +99,7 @@ fn Editable(cx: Scope<EditableProps>) -> Element {
             },
 
             p {
-                "High-Five counter: {count}",
+                "High-Five counter: {count.to_string():?}",
             }
 
             div {

+ 34 - 30
packages/core-macro/src/lib.rs

@@ -188,40 +188,44 @@ pub fn rsx(s: TokenStream) -> TokenStream {
             {
                 use dioxus_rsx_interperter::captuered_context::CapturedContextBuilder;
 
-                let captured = CapturedContextBuilder::from_call_body(body);
-                quote::quote! {
-                    {
-                        let __line_num = get_line_num();
-                        let __rsx_text_index: RsxTextIndex = cx.consume_context().unwrap();
-                        // only the insert the rsx text once
-                        if !__rsx_text_index.read().contains_key(&__line_num){
-                            __rsx_text_index.insert(
-                                __line_num.clone(),
-                                #rsx_text.to_string(),
-                            );
-                        }
-                        LazyNodes::new(move |__cx|{
-                            if let Some(__text) = {
-                                let read = __rsx_text_index.read();
-                                // clone prevents deadlock on nested rsx calls
-                                read.get(&__line_num).cloned()
-                            } {
-                                match interpert_rsx(
-                                    __cx,
-                                    &__text,
-                                    #captured
-                                ){
-                                    Ok(vnode) => vnode,
-                                    Err(err) => __cx.text(format_args!("{:?}", err))
+                match CapturedContextBuilder::from_call_body(body) {
+                    Ok(captured) => {
+                        quote::quote! {
+                            {
+                                let __line_num = get_line_num();
+                                let __rsx_text_index: RsxTextIndex = cx.consume_context().unwrap();
+                                // only the insert the rsx text once
+                                if !__rsx_text_index.read().contains_key(&__line_num){
+                                    __rsx_text_index.insert(
+                                        __line_num.clone(),
+                                        #rsx_text.to_string(),
+                                    );
                                 }
+                                LazyNodes::new(move |__cx|{
+                                    if let Some(__text) = {
+                                        let read = __rsx_text_index.read();
+                                        // clone prevents deadlock on nested rsx calls
+                                        read.get(&__line_num).cloned()
+                                    } {
+                                        match interpert_rsx(
+                                            __cx,
+                                            &__text,
+                                            #captured
+                                        ){
+                                            Ok(vnode) => vnode,
+                                            Err(err) => __cx.text(format_args!("{:?}", err))
+                                        }
+                                    }
+                                    else {
+                                        panic!("rsx: line number {:?} not found in rsx index", __line_num);
+                                    }
+                                })
                             }
-                            else {
-                                panic!("rsx: line number {:?} not found in rsx index", __line_num);
-                            }
-                        })
+                        }
+                        .into()
                     }
+                    Err(err) => err.into_compile_error().into(),
                 }
-                .into()
             }
             #[cfg(not(feature = "hot_reload"))]
             body.to_token_stream().into()

+ 1 - 5
packages/rsx/Cargo.toml

@@ -9,8 +9,4 @@ license = "MIT/Apache-2.0"
 [dependencies]
 proc-macro2 = { version = "1.0" }
 syn = { version = "1.0", features = ["full", "extra-traits"] }
-quote = { version = "1.0", optional = true }
-
-[features]
-default = ["to_tokens"]
-to_tokens = ["quote"]
+quote = { version = "1.0" }

+ 0 - 2
packages/rsx/src/component.rs

@@ -14,7 +14,6 @@
 use super::*;
 
 use proc_macro2::TokenStream as TokenStream2;
-#[cfg(feature = "to_tokens")]
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     ext::IdentExt,
@@ -73,7 +72,6 @@ impl Parse for Component {
     }
 }
 
-#[cfg(feature = "to_tokens")]
 impl ToTokens for Component {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;

+ 0 - 3
packages/rsx/src/element.rs

@@ -1,7 +1,6 @@
 use super::*;
 
 use proc_macro2::TokenStream as TokenStream2;
-#[cfg(feature = "to_tokens")]
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     parse::{Parse, ParseBuffer, ParseStream},
@@ -159,7 +158,6 @@ impl Parse for Element {
     }
 }
 
-#[cfg(feature = "to_tokens")]
 impl ToTokens for Element {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
@@ -218,7 +216,6 @@ pub struct ElementAttrNamed {
     pub attr: ElementAttr,
 }
 
-#[cfg(feature = "to_tokens")]
 impl ToTokens for ElementAttrNamed {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let ElementAttrNamed { el_name, attr } = self;

+ 127 - 208
packages/rsx/src/ifmt.rs

@@ -1,24 +1,60 @@
-#[cfg(feature = "to_tokens")]
-use ::quote::{quote, ToTokens};
-use ::std::ops::Not;
-use ::syn::{
+use proc_macro2::{Span, TokenStream};
+
+use quote::{quote, ToTokens};
+use syn::{
     parse::{Parse, ParseStream},
-    punctuated::Punctuated,
     *,
 };
-use proc_macro2::TokenStream;
 
-#[cfg(feature = "to_tokens")]
 pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
-    let IfmtInput {
-        format_literal,
-        positional_args,
-        named_args,
-    } = input;
+    // build format_literal
+    let mut format_literal = String::new();
+    let mut expr_counter = 0;
+    for segment in input.segments.iter() {
+        match segment {
+            Segment::Literal(s) => format_literal += &s,
+            Segment::Formatted {
+                format_args,
+                segment,
+            } => {
+                format_literal += "{";
+                match segment {
+                    FormattedSegment::Expr(_) => {
+                        format_literal += &expr_counter.to_string();
+                        expr_counter += 1;
+                    }
+                    FormattedSegment::Ident(ident) => {
+                        format_literal += &ident.to_string();
+                    }
+                }
+                format_literal += ":";
+                format_literal += format_args;
+                format_literal += "}";
+            }
+        }
+    }
+
+    let positional_args = input.segments.iter().filter_map(|seg| {
+        if let Segment::Formatted { segment, .. } = seg {
+            if let FormattedSegment::Expr(expr) = segment {
+                Some(expr)
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    });
 
-    let named_args = named_args.into_iter().map(|(ident, expr)| {
-        quote! {
-            #ident = #expr
+    let named_args = input.segments.iter().filter_map(|seg| {
+        if let Segment::Formatted { segment, .. } = seg {
+            if let FormattedSegment::Ident(ident) = segment {
+                Some(quote! {#ident = #ident})
+            } else {
+                None
+            }
+        } else {
+            None
         }
     });
 
@@ -34,219 +70,102 @@ pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
 #[allow(dead_code)] // dumb compiler does not see the struct being used...
 #[derive(Debug)]
 pub struct IfmtInput {
-    pub format_literal: LitStr,
-    pub positional_args: Vec<Expr>,
-    pub named_args: Vec<(Ident, Expr)>,
+    pub segments: Vec<Segment>,
 }
 
 impl IfmtInput {
-    fn parse_segments(self) -> Result<Self> {
-        let IfmtInput {
-            mut format_literal,
-            mut positional_args,
-            mut named_args,
-        } = self;
-
-        let s = format_literal.value();
-        let out_format_literal = &mut 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 = *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;
-            }
-
-            #[derive(Debug)]
-            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(err),
+    pub fn from_str(input: &str) -> Result<Self> {
+        let mut chars = input.chars().peekable();
+        let mut segments = Vec::new();
+        let mut current_literal = String::new();
+        while let Some(c) = chars.next() {
+            if c == '{' {
+                if let Some(c) = chars.next_if(|c| *c == '{') {
+                    current_literal.push(c);
+                    continue;
                 }
-            };
-            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
-                                ));
+                segments.push(Segment::Literal(current_literal));
+                current_literal = String::new();
+                let mut current_captured = String::new();
+                while let Some(c) = chars.next() {
+                    if c == ':' {
+                        let mut current_format_args = String::new();
+                        while let Some(c) = chars.next() {
+                            if c == '}' {
+                                segments.push(Segment::Formatted {
+                                    format_args: current_format_args,
+                                    segment: FormattedSegment::parse(&current_captured)?,
+                                });
+                                break;
                             }
+                            current_format_args.push(c);
                         }
+                        break;
                     }
+                    if c == '}' {
+                        segments.push(Segment::Formatted {
+                            format_args: String::new(),
+                            segment: FormattedSegment::parse(&current_captured)?,
+                        });
+                        break;
+                    }
+                    current_captured.push(c);
                 }
-                _ => {
-                    ::std::fmt::Write::write_fmt(
-                        out_format_literal,
-                        format_args!("{}", positional_args.len()),
-                    )
-                    .expect("`usize` or `char` Display impl cannot panic");
-                    let segments: Punctuated<TokenStream, 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
-                    })
-                }
+            } else {
+                current_literal.push(c);
             }
         }
-        format_literal = LitStr::new(out_format_literal, format_literal.span());
-
-        Ok(Self {
-            format_literal,
-            positional_args,
-            named_args,
-        })
+        segments.push(Segment::Literal(current_literal));
+        Ok(Self { segments })
     }
+}
 
-    fn parse_positional_args(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,
-        })
-    }
+#[derive(Debug)]
+pub enum Segment {
+    Literal(String),
+    Formatted {
+        format_args: String,
+        segment: FormattedSegment,
+    },
 }
 
-impl Parse for IfmtInput {
-    fn parse(input: ParseStream) -> Result<Self> {
-        Self::parse_positional_args(input).and_then(|new| new.parse_segments())
-    }
+#[derive(Debug)]
+pub enum FormattedSegment {
+    Expr(Expr),
+    Ident(Ident),
 }
 
-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(&'_ mut self) {
-            use ::core::ptr;
-            unsafe {
-                // # Safety
-                //
-                //   - This is the canonical example of using `ManuallyDrop`.
-                let value = ManuallyDrop::into_inner(ptr::read(&self.0));
-                let drop = ManuallyDrop::into_inner(ptr::read(&self.1));
-                drop(value);
+impl FormattedSegment {
+    fn parse(input: &str) -> Result<Self> {
+        if let Ok(ident) = parse_str::<Ident>(input) {
+            if &ident.to_string() == input {
+                return Ok(Self::Ident(ident));
             }
         }
-    }
-    impl<T, Drop> ::core::ops::Deref for Ret<T, Drop>
-    where
-        Drop: FnOnce(T),
-    {
-        type Target = T;
-        #[inline]
-        fn deref(&'_ self) -> &'_ Self::Target {
-            &self.0
+        if let Ok(expr) = parse_str(&("{".to_string() + input + "}")) {
+            Ok(Self::Expr(expr))
+        } else {
+            Err(Error::new(
+                Span::call_site(),
+                "Expected Ident or Expression",
+            ))
         }
     }
-    impl<T, Drop> ::core::ops::DerefMut for Ret<T, Drop>
-    where
-        Drop: FnOnce(T),
-    {
-        #[inline]
-        fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
-            &mut self.0
+}
+
+impl ToTokens for FormattedSegment {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Self::Expr(expr) => expr.to_tokens(tokens),
+            Self::Ident(ident) => ident.to_tokens(tokens),
         }
     }
-    Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop))
+}
+
+impl Parse for IfmtInput {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let input: LitStr = input.parse()?;
+        let input_str = input.value();
+        IfmtInput::from_str(&input_str)
+    }
 }

+ 0 - 2
packages/rsx/src/node.rs

@@ -1,7 +1,6 @@
 use super::*;
 
 use proc_macro2::TokenStream as TokenStream2;
-#[cfg(feature = "to_tokens")]
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     parse::{Parse, ParseStream},
@@ -79,7 +78,6 @@ impl Parse for BodyNode {
     }
 }
 
-#[cfg(feature = "to_tokens")]
 impl ToTokens for BodyNode {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match &self {

+ 47 - 32
packages/rsx_interperter/src/captuered_context.rs

@@ -1,8 +1,10 @@
 use dioxus_core::{Listener, VNode};
-use dioxus_rsx::{BodyNode, CallBody, Component, ElementAttr, ElementAttrNamed, IfmtInput};
+use dioxus_rsx::{
+    BodyNode, CallBody, Component, ElementAttr, ElementAttrNamed, IfmtInput, Segment,
+};
 use quote::{quote, ToTokens, TokenStreamExt};
 use std::collections::HashMap;
-use syn::Expr;
+use syn::{Expr, Result};
 
 #[derive(Default)]
 pub struct CapturedContextBuilder {
@@ -24,15 +26,15 @@ impl CapturedContextBuilder {
         self.captured_expressions.extend(other.captured_expressions);
     }
 
-    pub fn from_call_body(body: CallBody) -> Self {
+    pub fn from_call_body(body: CallBody) -> Result<Self> {
         let mut new = Self::default();
         for node in body.roots {
-            new.extend(Self::find_captured(node));
+            new.extend(Self::find_captured(node)?);
         }
-        new
+        Ok(new)
     }
 
-    fn find_captured(node: BodyNode) -> Self {
+    fn find_captured(node: BodyNode) -> Result<Self> {
         let mut captured = CapturedContextBuilder::default();
         match node {
             BodyNode::Element(el) => {
@@ -40,7 +42,7 @@ impl CapturedContextBuilder {
                     match attr.attr {
                         ElementAttr::AttrText { name, value } => {
                             let (name, value_tokens) = (name.to_string(), value.to_token_stream());
-                            let formated: IfmtInput = syn::parse2(value_tokens).unwrap();
+                            let formated: IfmtInput = syn::parse2(value_tokens)?;
                             captured.attributes.insert(name, formated);
                         }
                         ElementAttr::AttrExpression { name: _, value } => {
@@ -48,7 +50,7 @@ impl CapturedContextBuilder {
                         }
                         ElementAttr::CustomAttrText { name, value } => {
                             let (name, value_tokens) = (name.value(), value.to_token_stream());
-                            let formated: IfmtInput = syn::parse2(value_tokens).unwrap();
+                            let formated: IfmtInput = syn::parse2(value_tokens)?;
                             captured.attributes.insert(name, formated);
                         }
                         ElementAttr::CustomAttrExpression { name: _, value } => {
@@ -58,7 +60,7 @@ impl CapturedContextBuilder {
                     }
                 }
                 for child in el.children {
-                    captured.extend(Self::find_captured(child));
+                    captured.extend(Self::find_captured(child)?);
                 }
             }
             BodyNode::Component(comp) => {
@@ -71,7 +73,7 @@ impl CapturedContextBuilder {
             }
             BodyNode::RawExpr(_) => captured.iterators.push(node),
         }
-        captured
+        Ok(captured)
     }
 }
 
@@ -95,34 +97,41 @@ impl ToTokens for CapturedContextBuilder {
             BodyNode::RawExpr(expr) => expr.to_token_stream().to_string(),
             _ => unreachable!(),
         });
-        let captured_named: Vec<_> = attributes
-            .iter()
-            .map(|(_, fmt)| fmt.named_args.iter())
-            .chain(text.iter().map(|fmt| fmt.named_args.iter()))
-            .flatten()
-            .collect();
-        let captured_expr: Vec<_> = attributes
-            .iter()
-            .map(|(_, fmt)| fmt.positional_args.iter())
-            .chain(text.iter().map(|fmt| fmt.positional_args.iter()))
+        let captured: Vec<_> = attributes
+            .values()
+            .chain(text.iter())
+            .map(|input| input.segments.iter())
             .flatten()
+            .filter_map(|seg| match seg {
+                Segment::Formatted {
+                    format_args,
+                    segment,
+                } => {
+                    let expr = segment.to_token_stream();
+                    let as_string = expr.to_string();
+                    let format_expr = if format_args.is_empty() {
+                        "{".to_string() + format_args + "}"
+                    } else {
+                        "{".to_string() + ":" + format_args + "}"
+                    };
+                    Some(quote! {
+                        FormattedArg{
+                            expr: #as_string,
+                            format_args: #format_args,
+                            result: format!(#format_expr, #expr)
+                        }
+                    })
+                }
+                _ => None,
+            })
             .collect();
-        let captured_names = captured_named.iter().map(|(n, _)| n.to_string()).chain(
-            captured_expr
-                .iter()
-                .map(|expr| expr.to_token_stream().to_string()),
-        );
-        let captured_expr = captured_named
-            .iter()
-            .map(|(_, e)| e)
-            .chain(captured_expr.iter().map(|expr| *expr));
         let captured_attr_expressions_text = captured_expressions
             .iter()
             .map(|e| format!("{}", e.to_token_stream()));
         tokens.append_all(quote! {
             CapturedContext {
                 captured: IfmtArgs{
-                    named_args: vec![#((#captured_names, #captured_expr.to_string())),*]
+                    named_args: vec![#(#captured),*]
                 },
                 components: vec![#((#compontents_str, #components)),*],
                 iterators: vec![#((#iterators_str, #iterators)),*],
@@ -149,6 +158,12 @@ pub struct CapturedContext<'a> {
 }
 
 pub struct IfmtArgs {
-    // live reload only supports named arguments
-    pub named_args: Vec<(&'static str, String)>,
+    // map expressions to the value string they produced
+    pub named_args: Vec<FormattedArg>,
+}
+
+pub struct FormattedArg {
+    pub expr: &'static str,
+    pub format_args: &'static str,
+    pub result: String,
 }

+ 41 - 74
packages/rsx_interperter/src/interperter.rs

@@ -1,7 +1,6 @@
 use dioxus_core::{Attribute, AttributeValue, NodeFactory, VNode};
-use dioxus_rsx::{BodyNode, CallBody, ElementAttr};
+use dioxus_rsx::{BodyNode, CallBody, ElementAttr, IfmtInput, Segment};
 use quote::ToTokens;
-use std::str::FromStr;
 use syn::{parse2, parse_str, Expr};
 
 use crate::attributes::attrbute_to_static_str;
@@ -9,28 +8,31 @@ use crate::captuered_context::{CapturedContext, IfmtArgs};
 use crate::elements::element_to_static_str;
 use crate::error::{Error, RecompileReason};
 
-#[derive(Debug)]
-enum Segment {
-    Ident(String),
-    Literal(String),
-}
-
-struct InterperedIfmt {
-    segments: Vec<Segment>,
-}
+struct InterpertedIfmt(IfmtInput);
 
-impl InterperedIfmt {
+impl InterpertedIfmt {
     fn resolve(&self, captured: &IfmtArgs) -> String {
         let mut result = String::new();
-        for seg in &self.segments {
+        for seg in &self.0.segments {
             match seg {
-                Segment::Ident(name) => {
-                    let (_, value) = captured
+                Segment::Formatted {
+                    segment,
+                    format_args,
+                } => {
+                    let expr = segment.to_token_stream();
+                    let expr_str = expr.to_string();
+                    let expr: Expr = parse2(expr).unwrap();
+                    let formatted = captured
                         .named_args
                         .iter()
-                        .find(|(n, _)| *n == name)
-                        .expect(format!("could not resolve {}", name).as_str());
-                    result.push_str(value);
+                        .find(|fmted| {
+                            parse_str::<Expr>(fmted.expr).unwrap() == expr
+                                && fmted.format_args == format_args
+                        })
+                        .expect(
+                            format!("could not resolve {{{}:{}}}", expr_str, format_args).as_str(),
+                        );
+                    result.push_str(&formatted.result);
                 }
                 Segment::Literal(lit) => result.push_str(lit),
             }
@@ -39,52 +41,6 @@ impl InterperedIfmt {
     }
 }
 
-impl FromStr for InterperedIfmt {
-    type Err = ();
-    fn from_str(input: &str) -> Result<Self, ()> {
-        let mut segments = Vec::new();
-        let mut segment = String::new();
-        let mut chars = input.chars().peekable();
-        while let Some(c) = chars.next() {
-            if c == '{' {
-                if chars.peek().copied() != Some('{') {
-                    let old;
-                    (old, segment) = (segment, String::new());
-                    if !old.is_empty() {
-                        segments.push(Segment::Literal(old));
-                    }
-                    while let Some(c) = chars.next() {
-                        if c == '}' {
-                            let old;
-                            (old, segment) = (segment, String::new());
-                            if !old.is_empty() {
-                                segments.push(Segment::Ident(old));
-                            }
-                            break;
-                        }
-                        if c == ':' {
-                            while Some('}') != chars.next() {}
-                            let old;
-                            (old, segment) = (segment, String::new());
-                            if !old.is_empty() {
-                                segments.push(Segment::Ident(old));
-                            }
-                            break;
-                        }
-                        segment.push(c);
-                    }
-                }
-            } else {
-                segment.push(c);
-            }
-        }
-        if !segment.is_empty() {
-            segments.push(Segment::Literal(segment));
-        }
-        Ok(Self { segments })
-    }
-}
-
 pub fn build<'a>(
     rsx: CallBody,
     mut ctx: CapturedContext<'a>,
@@ -105,7 +61,9 @@ fn build_node<'a>(
     let bump = factory.bump();
     match node {
         BodyNode::Text(text) => {
-            let ifmt: InterperedIfmt = text.value().parse().unwrap();
+            let ifmt = InterpertedIfmt(
+                IfmtInput::from_str(&text.value()).map_err(|err| Error::ParseError(err))?,
+            );
             let text = bump.alloc(ifmt.resolve(&ctx.captured));
             Ok(factory.text(format_args!("{}", text)))
         }
@@ -114,13 +72,21 @@ fn build_node<'a>(
             for attr in &el.attributes {
                 match &attr.attr {
                     ElementAttr::AttrText { .. } | ElementAttr::CustomAttrText { .. } => {
-                        let (name, value): (String, InterperedIfmt) = match &attr.attr {
-                            ElementAttr::AttrText { name, value } => {
-                                (name.to_string(), value.value().parse().unwrap())
-                            }
-                            ElementAttr::CustomAttrText { name, value } => {
-                                (name.value(), value.value().parse().unwrap())
-                            }
+                        let (name, value): (String, InterpertedIfmt) = match &attr.attr {
+                            ElementAttr::AttrText { name, value } => (
+                                name.to_string(),
+                                InterpertedIfmt(
+                                    IfmtInput::from_str(&value.value())
+                                        .map_err(|err| Error::ParseError(err))?,
+                                ),
+                            ),
+                            ElementAttr::CustomAttrText { name, value } => (
+                                name.value(),
+                                InterpertedIfmt(
+                                    IfmtInput::from_str(&value.value())
+                                        .map_err(|err| Error::ParseError(err))?,
+                                ),
+                            ),
                             _ => unreachable!(),
                         };
 
@@ -145,7 +111,6 @@ fn build_node<'a>(
                             ElementAttr::CustomAttrExpression { name, value } => {
                                 (name.value(), value)
                             }
-
                             _ => unreachable!(),
                         };
                         if let Some((_, resulting_value)) = ctx
@@ -219,7 +184,9 @@ fn build_node<'a>(
                         None,
                     )),
                     Some(lit) => {
-                        let ifmt: InterperedIfmt = lit.value().parse().unwrap();
+                        let ifmt: InterpertedIfmt = InterpertedIfmt(
+                            parse_str(&lit.value()).map_err(|err| Error::ParseError(err))?,
+                        );
                         let key = bump.alloc(ifmt.resolve(&ctx.captured));
 
                         Ok(factory.raw_element(

+ 1 - 1
src/lib.rs

@@ -61,7 +61,7 @@ pub mod prelude {
 
     #[cfg(feature = "hot_reload")]
     pub use dioxus_rsx_interperter::{
-        captuered_context::{CapturedContext, IfmtArgs},
+        captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
         get_line_num, interpert_rsx, with_hot_reload, CodeLocation, RsxTextIndex,
     };
 }