handle formatting options

This commit is contained in:
Evan Almloff committed 2022-05-30 18:32:57 -05:00
1 parent 2183ecf3fb
commit 94448ea4aa
10 files changed
+278 -386

No files matched your search

+26 -28
View File
@@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element {
}, },
p { p {
"High-Five counter: {count}", "High-Five counter: {count.to_string():?}",
} }
div { div {
@@ -43,33 +43,31 @@ fn app(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { div {
display: "flex", display: "flex",
flex_direction: "row", flex_direction: "row",
width: "100%", width: "100%",
height: "50%", height: "50%",
Editable{ Editable{
current_code: submitted_rsx_code.get().clone(), current_code: submitted_rsx_code.get().clone(),
}, },
textarea { textarea {
width: "90%", width: "90%",
value: value: rsx_code,
rsx_code oninput: move |evt| {
, rsx_code.set(evt.value.clone());
oninput: move |evt| { },
rsx_code.set(evt.value.clone()); }
},
}
button { button {
height: "100%", height: "100%",
width: "10%", width: "10%",
onclick: move |_|{ onclick: move |_|{
submitted_rsx_code.set(Some(rsx_code.get().clone())); submitted_rsx_code.set(Some(rsx_code.get().clone()));
}, },
"submit" "submit"
} }
} }
}) })
} }
@@ -86,7 +84,7 @@ fn Editable(cx: Scope<EditableProps>) -> Element {
rsx_index.insert( rsx_index.insert(
CodeLocation { CodeLocation {
file: r"examples\hot_reload.rs".to_string(), file: r"examples\hot_reload.rs".to_string(),
line: 95, line: 93,
column: 15, column: 15,
}, },
code.clone(), code.clone(),
@@ -101,7 +99,7 @@ fn Editable(cx: Scope<EditableProps>) -> Element {
}, },
p { p {
"High-Five counter: {count}", "High-Five counter: {count.to_string():?}",
} }
div { div {
+34 -30
View File
@@ -188,40 +188,44 @@ pub fn rsx(s: TokenStream) -> TokenStream {
{ {
use dioxus_rsx_interperter::captuered_context::CapturedContextBuilder; use dioxus_rsx_interperter::captuered_context::CapturedContextBuilder;
let captured = CapturedContextBuilder::from_call_body(body); match CapturedContextBuilder::from_call_body(body) {
quote::quote! { Ok(captured) => {
{ quote::quote! {
let __line_num = get_line_num(); {
let __rsx_text_index: RsxTextIndex = cx.consume_context().unwrap(); let __line_num = get_line_num();
// only the insert the rsx text once let __rsx_text_index: RsxTextIndex = cx.consume_context().unwrap();
if !__rsx_text_index.read().contains_key(&__line_num){ // only the insert the rsx text once
__rsx_text_index.insert( if !__rsx_text_index.read().contains_key(&__line_num){
__line_num.clone(), __rsx_text_index.insert(
#rsx_text.to_string(), __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))
} }
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"))] #[cfg(not(feature = "hot_reload"))]
body.to_token_stream().into() body.to_token_stream().into()
+1 -5
View File
@@ -9,8 +9,4 @@ license = "MIT/Apache-2.0"
[dependencies] [dependencies]
proc-macro2 = { version = "1.0" } proc-macro2 = { version = "1.0" }
syn = { version = "1.0", features = ["full", "extra-traits"] } syn = { version = "1.0", features = ["full", "extra-traits"] }
quote = { version = "1.0", optional = true } quote = { version = "1.0" }
[features]
default = ["to_tokens"]
to_tokens = ["quote"]
-2
View File
@@ -14,7 +14,6 @@
use super::*; use super::*;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
#[cfg(feature = "to_tokens")]
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
ext::IdentExt, ext::IdentExt,
@@ -73,7 +72,6 @@ impl Parse for Component {
} }
} }
#[cfg(feature = "to_tokens")]
impl ToTokens for Component { impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name; let name = &self.name;
-3
View File
@@ -1,7 +1,6 @@
use super::*; use super::*;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
#[cfg(feature = "to_tokens")]
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
@@ -159,7 +158,6 @@ impl Parse for Element {
} }
} }
#[cfg(feature = "to_tokens")]
impl ToTokens for Element { impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name; let name = &self.name;
@@ -218,7 +216,6 @@ pub struct ElementAttrNamed {
pub attr: ElementAttr, pub attr: ElementAttr,
} }
#[cfg(feature = "to_tokens")]
impl ToTokens for ElementAttrNamed { impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self; let ElementAttrNamed { el_name, attr } = self;
+128 -209
View File
@@ -1,24 +1,60 @@
#[cfg(feature = "to_tokens")] use proc_macro2::{Span, TokenStream};
use ::quote::{quote, ToTokens};
use ::std::ops::Not; use quote::{quote, ToTokens};
use ::syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
punctuated::Punctuated,
*, *,
}; };
use proc_macro2::TokenStream;
#[cfg(feature = "to_tokens")]
pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> { pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
let IfmtInput { // build format_literal
format_literal, let mut format_literal = String::new();
positional_args, let mut expr_counter = 0;
named_args, for segment in input.segments.iter() {
} = input; 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 named_args = named_args.into_iter().map(|(ident, expr)| { let positional_args = input.segments.iter().filter_map(|seg| {
quote! { if let Segment::Formatted { segment, .. } = seg {
#ident = #expr if let FormattedSegment::Expr(expr) = segment {
Some(expr)
} else {
None
}
} else {
None
}
});
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... #[allow(dead_code)] // dumb compiler does not see the struct being used...
#[derive(Debug)] #[derive(Debug)]
pub struct IfmtInput { pub struct IfmtInput {
pub format_literal: LitStr, pub segments: Vec<Segment>,
pub positional_args: Vec<Expr>,
pub named_args: Vec<(Ident, Expr)>,
} }
impl IfmtInput { impl IfmtInput {
fn parse_segments(self) -> Result<Self> { pub fn from_str(input: &str) -> Result<Self> {
let IfmtInput { let mut chars = input.chars().peekable();
mut format_literal, let mut segments = Vec::new();
mut positional_args, let mut current_literal = String::new();
mut named_args, while let Some(c) = chars.next() {
} = self; if c == '{' {
if let Some(c) = chars.next_if(|c| *c == '{') {
let s = format_literal.value(); current_literal.push(c);
let out_format_literal = &mut String::with_capacity(s.len()); continue;
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( segments.push(Segment::Literal(current_literal));
Punctuated::<Segment, Token![.]>::parse_separated_nonempty, current_literal = String::new();
arg, let mut current_captured = String::new();
) { while let Some(c) = chars.next() {
Ok(segments) => segments.into_iter().collect(), if c == ':' {
Err(err) => return Err(err), let mut current_format_args = String::new();
} while let Some(c) = chars.next() {
}; if c == '}' {
match segments.len() { segments.push(Segment::Formatted {
0 => unreachable!("`parse_separated_nonempty` returned empty"), format_args: current_format_args,
1 => { segment: FormattedSegment::parse(&current_captured)?,
out_format_literal.push_str(arg); });
match { segments }.pop().unwrap() { break;
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
));
} }
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);
} }
_ => { } else {
::std::fmt::Write::write_fmt( current_literal.push(c);
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
})
}
} }
} }
format_literal = LitStr::new(out_format_literal, format_literal.span()); segments.push(Segment::Literal(current_literal));
Ok(Self { segments })
Ok(Self {
format_literal,
positional_args,
named_args,
})
} }
}
fn parse_positional_args(input: ParseStream) -> Result<Self> { #[derive(Debug)]
let format_literal = input.parse()?; pub enum Segment {
let mut positional_args = vec![]; Literal(String),
loop { Formatted {
if input.parse::<Option<Token![,]>>()?.is_none() { format_args: String,
return Ok(Self { segment: FormattedSegment,
format_literal, },
positional_args, }
named_args: vec![],
}); #[derive(Debug)]
pub enum FormattedSegment {
Expr(Expr),
Ident(Ident),
}
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));
} }
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| { if let Ok(expr) = parse_str(&("{".to_string() + input + "}")) {
Ok({ Ok(Self::Expr(expr))
let name: Ident = input.parse()?; } else {
let _: Token![=] = input.parse()?; Err(Error::new(
let expr: Expr = input.parse()?; Span::call_site(),
(name, expr) "Expected Ident or Expression",
}) ))
})? }
.into_iter() }
.collect(); }
Ok(Self { impl ToTokens for FormattedSegment {
format_literal, fn to_tokens(&self, tokens: &mut TokenStream) {
positional_args, match self {
named_args, Self::Expr(expr) => expr.to_tokens(tokens),
}) Self::Ident(ident) => ident.to_tokens(tokens),
}
} }
} }
impl Parse for IfmtInput { impl Parse for IfmtInput {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> Result<Self> {
Self::parse_positional_args(input).and_then(|new| new.parse_segments()) let input: LitStr = input.parse()?;
let input_str = input.value();
IfmtInput::from_str(&input_str)
} }
} }
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<T, Drop> ::core::ops::Deref for Ret<T, Drop>
where
Drop: FnOnce(T),
{
type Target = T;
#[inline]
fn deref(&'_ self) -> &'_ Self::Target {
&self.0
}
}
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
}
}
Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop))
}
-2
View File
@@ -1,7 +1,6 @@
use super::*; use super::*;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
#[cfg(feature = "to_tokens")]
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
@@ -79,7 +78,6 @@ impl Parse for BodyNode {
} }
} }
#[cfg(feature = "to_tokens")]
impl ToTokens for BodyNode { impl ToTokens for BodyNode {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self { match &self {
@@ -1,8 +1,10 @@
use dioxus_core::{Listener, VNode}; 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 quote::{quote, ToTokens, TokenStreamExt};
use std::collections::HashMap; use std::collections::HashMap;
use syn::Expr; use syn::{Expr, Result};
#[derive(Default)] #[derive(Default)]
pub struct CapturedContextBuilder { pub struct CapturedContextBuilder {
@@ -24,15 +26,15 @@ impl CapturedContextBuilder {
self.captured_expressions.extend(other.captured_expressions); 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(); let mut new = Self::default();
for node in body.roots { 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(); let mut captured = CapturedContextBuilder::default();
match node { match node {
BodyNode::Element(el) => { BodyNode::Element(el) => {
@@ -40,7 +42,7 @@ impl CapturedContextBuilder {
match attr.attr { match attr.attr {
ElementAttr::AttrText { name, value } => { ElementAttr::AttrText { name, value } => {
let (name, value_tokens) = (name.to_string(), value.to_token_stream()); 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); captured.attributes.insert(name, formated);
} }
ElementAttr::AttrExpression { name: _, value } => { ElementAttr::AttrExpression { name: _, value } => {
@@ -48,7 +50,7 @@ impl CapturedContextBuilder {
} }
ElementAttr::CustomAttrText { name, value } => { ElementAttr::CustomAttrText { name, value } => {
let (name, value_tokens) = (name.value(), value.to_token_stream()); 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); captured.attributes.insert(name, formated);
} }
ElementAttr::CustomAttrExpression { name: _, value } => { ElementAttr::CustomAttrExpression { name: _, value } => {
@@ -58,7 +60,7 @@ impl CapturedContextBuilder {
} }
} }
for child in el.children { for child in el.children {
captured.extend(Self::find_captured(child)); captured.extend(Self::find_captured(child)?);
} }
} }
BodyNode::Component(comp) => { BodyNode::Component(comp) => {
@@ -71,7 +73,7 @@ impl CapturedContextBuilder {
} }
BodyNode::RawExpr(_) => captured.iterators.push(node), 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(), BodyNode::RawExpr(expr) => expr.to_token_stream().to_string(),
_ => unreachable!(), _ => unreachable!(),
}); });
let captured_named: Vec<_> = attributes let captured: Vec<_> = attributes
.iter() .values()
.map(|(_, fmt)| fmt.named_args.iter()) .chain(text.iter())
.chain(text.iter().map(|fmt| fmt.named_args.iter())) .map(|input| input.segments.iter())
.flatten() .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(); .collect();
let captured_expr: Vec<_> = attributes
.iter()
.map(|(_, fmt)| fmt.positional_args.iter())
.chain(text.iter().map(|fmt| fmt.positional_args.iter()))
.flatten()
.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 let captured_attr_expressions_text = captured_expressions
.iter() .iter()
.map(|e| format!("{}", e.to_token_stream())); .map(|e| format!("{}", e.to_token_stream()));
tokens.append_all(quote! { tokens.append_all(quote! {
CapturedContext { CapturedContext {
captured: IfmtArgs{ captured: IfmtArgs{
named_args: vec![#((#captured_names, #captured_expr.to_string())),*] named_args: vec![#(#captured),*]
}, },
components: vec![#((#compontents_str, #components)),*], components: vec![#((#compontents_str, #components)),*],
iterators: vec![#((#iterators_str, #iterators)),*], iterators: vec![#((#iterators_str, #iterators)),*],
@@ -149,6 +158,12 @@ pub struct CapturedContext<'a> {
} }
pub struct IfmtArgs { pub struct IfmtArgs {
// live reload only supports named arguments // map expressions to the value string they produced
pub named_args: Vec<(&'static str, String)>, pub named_args: Vec<FormattedArg>,
}
pub struct FormattedArg {
pub expr: &'static str,
pub format_args: &'static str,
pub result: String,
} }
+41 -74
View File
@@ -1,7 +1,6 @@
use dioxus_core::{Attribute, AttributeValue, NodeFactory, VNode}; 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 quote::ToTokens;
use std::str::FromStr;
use syn::{parse2, parse_str, Expr}; use syn::{parse2, parse_str, Expr};
use crate::attributes::attrbute_to_static_str; 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::elements::element_to_static_str;
use crate::error::{Error, RecompileReason}; use crate::error::{Error, RecompileReason};
#[derive(Debug)] struct InterpertedIfmt(IfmtInput);
enum Segment {
Ident(String),
Literal(String),
}
struct InterperedIfmt { impl InterpertedIfmt {
segments: Vec<Segment>,
}
impl InterperedIfmt {
fn resolve(&self, captured: &IfmtArgs) -> String { fn resolve(&self, captured: &IfmtArgs) -> String {
let mut result = String::new(); let mut result = String::new();
for seg in &self.segments { for seg in &self.0.segments {
match seg { match seg {
Segment::Ident(name) => { Segment::Formatted {
let (_, value) = captured 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 .named_args
.iter() .iter()
.find(|(n, _)| *n == name) .find(|fmted| {
.expect(format!("could not resolve {}", name).as_str()); parse_str::<Expr>(fmted.expr).unwrap() == expr
result.push_str(value); && 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), 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>( pub fn build<'a>(
rsx: CallBody, rsx: CallBody,
mut ctx: CapturedContext<'a>, mut ctx: CapturedContext<'a>,
@@ -105,7 +61,9 @@ fn build_node<'a>(
let bump = factory.bump(); let bump = factory.bump();
match node { match node {
BodyNode::Text(text) => { 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)); let text = bump.alloc(ifmt.resolve(&ctx.captured));
Ok(factory.text(format_args!("{}", text))) Ok(factory.text(format_args!("{}", text)))
} }
@@ -114,13 +72,21 @@ fn build_node<'a>(
for attr in &el.attributes { for attr in &el.attributes {
match &attr.attr { match &attr.attr {
ElementAttr::AttrText { .. } | ElementAttr::CustomAttrText { .. } => { ElementAttr::AttrText { .. } | ElementAttr::CustomAttrText { .. } => {
let (name, value): (String, InterperedIfmt) = match &attr.attr { let (name, value): (String, InterpertedIfmt) = match &attr.attr {
ElementAttr::AttrText { name, value } => { ElementAttr::AttrText { name, value } => (
(name.to_string(), value.value().parse().unwrap()) name.to_string(),
} InterpertedIfmt(
ElementAttr::CustomAttrText { name, value } => { IfmtInput::from_str(&value.value())
(name.value(), value.value().parse().unwrap()) .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!(), _ => unreachable!(),
}; };
@@ -145,7 +111,6 @@ fn build_node<'a>(
ElementAttr::CustomAttrExpression { name, value } => { ElementAttr::CustomAttrExpression { name, value } => {
(name.value(), value) (name.value(), value)
} }
_ => unreachable!(), _ => unreachable!(),
}; };
if let Some((_, resulting_value)) = ctx if let Some((_, resulting_value)) = ctx
@@ -219,7 +184,9 @@ fn build_node<'a>(
None, None,
)), )),
Some(lit) => { 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)); let key = bump.alloc(ifmt.resolve(&ctx.captured));
Ok(factory.raw_element( Ok(factory.raw_element(
+1 -1
View File
@@ -61,7 +61,7 @@ pub mod prelude {
#[cfg(feature = "hot_reload")] #[cfg(feature = "hot_reload")]
pub use dioxus_rsx_interperter::{ pub use dioxus_rsx_interperter::{
captuered_context::{CapturedContext, IfmtArgs}, captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
get_line_num, interpert_rsx, with_hot_reload, CodeLocation, RsxTextIndex, get_line_num, interpert_rsx, with_hot_reload, CodeLocation, RsxTextIndex,
}; };
} }