1use proc_macro2::Span;
2use quote::quote;
3use quote::ToTokens;
4use std::fmt::Display;
5use std::ops::Deref;
6use syn::{
7 parse::{Parse, ParseStream},
8 Lit, LitBool, LitFloat, LitInt, LitStr,
9};
10
11use crate::{location::DynIdx, IfmtInput, Segment};
12use proc_macro2::TokenStream as TokenStream2;
13
14#[derive(PartialEq, Eq, Clone, Debug, Hash)]
22pub enum HotLiteral {
23 Fmted(HotReloadFormattedSegment),
30
31 Float(LitFloat),
35
36 Int(LitInt),
40
41 Bool(LitBool),
45}
46
47impl HotLiteral {
48 pub fn quote_as_hot_reload_literal(&self) -> TokenStream2 {
49 match &self {
50 HotLiteral::Fmted(f) => quote! { dioxus_core::internal::HotReloadLiteral::Fmted(#f) },
51 HotLiteral::Float(f) => {
52 quote! { dioxus_core::internal::HotReloadLiteral::Float(#f as _) }
53 }
54 HotLiteral::Int(f) => quote! { dioxus_core::internal::HotReloadLiteral::Int(#f as _) },
55 HotLiteral::Bool(f) => quote! { dioxus_core::internal::HotReloadLiteral::Bool(#f) },
56 }
57 }
58}
59
60impl Parse for HotLiteral {
61 fn parse(input: ParseStream) -> syn::Result<Self> {
62 let raw = input.parse::<Lit>()?;
63
64 let value = match raw.clone() {
65 Lit::Int(a) => HotLiteral::Int(a),
66 Lit::Bool(a) => HotLiteral::Bool(a),
67 Lit::Float(a) => HotLiteral::Float(a),
68 Lit::Str(a) => HotLiteral::Fmted(IfmtInput::new_litstr(a)?.into()),
69 _ => {
70 return Err(syn::Error::new(
71 raw.span(),
72 "Only string, int, float, and bool literals are supported",
73 ))
74 }
75 };
76
77 Ok(value)
78 }
79}
80
81impl ToTokens for HotLiteral {
82 fn to_tokens(&self, out: &mut proc_macro2::TokenStream) {
83 match &self {
84 HotLiteral::Fmted(f) => {
85 f.formatted_input.to_tokens(out);
86 }
87 HotLiteral::Float(f) => f.to_tokens(out),
88 HotLiteral::Int(f) => f.to_tokens(out),
89 HotLiteral::Bool(f) => f.to_tokens(out),
90 }
91 }
92}
93
94impl HotLiteral {
95 pub fn span(&self) -> Span {
96 match self {
97 HotLiteral::Fmted(f) => f.span(),
98 HotLiteral::Float(f) => f.span(),
99 HotLiteral::Int(f) => f.span(),
100 HotLiteral::Bool(f) => f.span(),
101 }
102 }
103}
104
105impl HotLiteral {
106 pub fn peek(input: ParseStream) -> bool {
109 if input.peek(Lit) {
110 let lit = input.fork().parse::<Lit>().unwrap();
111
112 matches!(
113 lit,
114 Lit::Str(_) | Lit::Int(_) | Lit::Float(_) | Lit::Bool(_)
115 )
116 } else {
117 false
118 }
119 }
120
121 pub fn is_static(&self) -> bool {
122 match &self {
123 HotLiteral::Fmted(fmt) => fmt.is_static(),
124 _ => false,
125 }
126 }
127
128 pub fn from_raw_text(text: &str) -> Self {
129 HotLiteral::Fmted(HotReloadFormattedSegment::from(IfmtInput {
130 source: LitStr::new(text, Span::call_site()),
131 segments: vec![],
132 }))
133 }
134}
135
136impl Display for HotLiteral {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 match &self {
139 HotLiteral::Fmted(l) => l.to_string_with_quotes().fmt(f),
140 HotLiteral::Float(l) => l.fmt(f),
141 HotLiteral::Int(l) => l.fmt(f),
142 HotLiteral::Bool(l) => l.value().fmt(f),
143 }
144 }
145}
146
147#[derive(PartialEq, Eq, Clone, Debug, Hash)]
149pub struct HotReloadFormattedSegment {
150 pub formatted_input: IfmtInput,
151 pub dynamic_node_indexes: Vec<DynIdx>,
152}
153
154impl HotReloadFormattedSegment {
155 pub fn span(&self) -> Span {
158 self.formatted_input.span()
159 }
160}
161
162impl Deref for HotReloadFormattedSegment {
163 type Target = IfmtInput;
164
165 fn deref(&self) -> &Self::Target {
166 &self.formatted_input
167 }
168}
169
170impl From<IfmtInput> for HotReloadFormattedSegment {
171 fn from(input: IfmtInput) -> Self {
172 let mut dynamic_node_indexes = Vec::new();
173 for segment in &input.segments {
174 if let Segment::Formatted { .. } = segment {
175 dynamic_node_indexes.push(DynIdx::default());
176 }
177 }
178 Self {
179 formatted_input: input,
180 dynamic_node_indexes,
181 }
182 }
183}
184
185impl Parse for HotReloadFormattedSegment {
186 fn parse(input: ParseStream) -> syn::Result<Self> {
187 let ifmt: IfmtInput = input.parse()?;
188 Ok(Self::from(ifmt))
189 }
190}
191
192impl ToTokens for HotReloadFormattedSegment {
193 fn to_tokens(&self, tokens: &mut TokenStream2) {
194 let mut idx = 0_usize;
195 let segments = self.segments.iter().map(|s| match s {
196 Segment::Literal(lit) => quote! {
197 dioxus_core::internal::FmtSegment::Literal { value: #lit }
198 },
199 Segment::Formatted(_fmt) => {
200 let _idx = self.dynamic_node_indexes[idx].get();
202 idx += 1;
203 quote! {
204 dioxus_core::internal::FmtSegment::Dynamic { id: #_idx }
205 }
206 }
207 });
208
209 tokens.extend(quote! {
211 dioxus_core::internal::FmtedSegments::new( vec![ #(#segments),* ], )
212 });
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use prettier_please::PrettyUnparse;
220
221 #[test]
222 fn parses_lits() {
223 let _ = syn::parse2::<HotLiteral>(quote! { "hello" }).unwrap();
224 let _ = syn::parse2::<HotLiteral>(quote! { "hello {world}" }).unwrap();
225 let _ = syn::parse2::<HotLiteral>(quote! { 1 }).unwrap();
226 let _ = syn::parse2::<HotLiteral>(quote! { 1.0 }).unwrap();
227 let _ = syn::parse2::<HotLiteral>(quote! { false }).unwrap();
228 let _ = syn::parse2::<HotLiteral>(quote! { true }).unwrap();
229
230 assert!(syn::parse2::<HotLiteral>(quote! { b"123" }).is_err());
232 assert!(syn::parse2::<HotLiteral>(quote! { 'a' }).is_err());
233
234 let lit = syn::parse2::<HotLiteral>(quote! { "hello" }).unwrap();
235 assert!(matches!(lit, HotLiteral::Fmted(_)));
236
237 let lit = syn::parse2::<HotLiteral>(quote! { "hello {world}" }).unwrap();
238 assert!(matches!(lit, HotLiteral::Fmted(_)));
239 }
240
241 #[test]
242 fn outputs_a_signal() {
243 let lit = syn::parse2::<HotLiteral>(quote! { 1.0 }).unwrap();
246 println!("{}", lit.to_token_stream().pretty_unparse());
247
248 let lit = syn::parse2::<HotLiteral>(quote! { "hi" }).unwrap();
249 println!("{}", lit.to_token_stream().pretty_unparse());
250
251 let lit = syn::parse2::<HotLiteral>(quote! { "hi {world}" }).unwrap();
252 println!("{}", lit.to_token_stream().pretty_unparse());
253 }
254
255 #[test]
256 fn static_str_becomes_str() {
257 let lit = syn::parse2::<HotLiteral>(quote! { "hello" }).unwrap();
258 let HotLiteral::Fmted(segments) = &lit else {
259 panic!("expected a formatted string");
260 };
261 assert!(segments.is_static());
262 assert_eq!(r##""hello""##, segments.to_string_with_quotes());
263 println!("{}", lit.to_token_stream().pretty_unparse());
264 }
265
266 #[test]
267 fn formatted_prints_as_formatted() {
268 let lit = syn::parse2::<HotLiteral>(quote! { "hello {world}" }).unwrap();
269 let HotLiteral::Fmted(segments) = &lit else {
270 panic!("expected a formatted string");
271 };
272 assert!(!segments.is_static());
273 assert_eq!(r##""hello {world}""##, segments.to_string_with_quotes());
274 println!("{}", lit.to_token_stream().pretty_unparse());
275 }
276}