1use proc_macro::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, ItemFn, visit_mut::{self, VisitMut}, Expr, ExprCall, ExprPath, Lit, ExprLit};
5use regex::Regex;
6
7struct FStringTransformer;
8
9impl VisitMut for FStringTransformer {
10 fn visit_expr_mut(&mut self, expr: &mut Expr) {
11 if let Expr::Call(ExprCall { func, args, .. }) = expr {
12 if let Expr::Path(ExprPath { path, .. }) = func.as_ref() {
13 if path.segments.len() == 1 &&
14 (path.segments[0].ident == "println" || path.segments[0].ident == "print") {
15 if let Some(Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. })) = args.first() {
16 let template = lit_str.value();
17 let transformations = extract_and_process_variables(&template);
18 if !transformations.is_empty() {
19 let func_name = &path.segments[0].ident;
20 let mut replacement_code = quote! {
21 let mut result = String::from(#template);
22 };
23 for (original, expr_str, format_spec) in transformations {
24 let expr_tokens = match expr_str.parse::<proc_macro2::TokenStream>() {
26 Ok(tokens) => tokens,
27 Err(_) => {
28 quote! { #expr_str }
30 }
31 };
32
33 let format_expr = match format_spec.as_deref() {
34 Some(":c") => quote! { format!("{:?}", #expr_tokens) },
35 Some(":j") => quote! {
36 {
37 let debug_str = format!("{:#?}", #expr_tokens);
38 pyrs_format_json_clean(&debug_str)
39 }
40 },
41 Some(":.2") => quote! { format!("{:.2}", #expr_tokens) },
42 Some(":.6") => quote! { format!("{:.6}", #expr_tokens) },
43 Some(":e") => quote! { format!("{:e}", #expr_tokens) },
44 Some(":.0") => quote! { format!("{:.0}", #expr_tokens) },
45 Some(":04") => quote! { format!("{:04}", #expr_tokens) },
46 Some(":x") => quote! { format!("{:x}", #expr_tokens) },
47 Some(":X") => quote! { format!("{:X}", #expr_tokens) },
48 Some(":b") => quote! { format!("{:b}", #expr_tokens) },
49 Some(":o") => quote! { format!("{:o}", #expr_tokens) },
50 None => quote! { format!("{}", #expr_tokens) },
51 _ => quote! { format!("{}", #expr_tokens) },
52 };
53 replacement_code = quote! {
54 #replacement_code
55 result = result.replace(#original, &#format_expr);
56 };
57 }
58 let new_expr = quote! {
59 #func_name({
60 #replacement_code
61 result
62 })
63 };
64 *expr = syn::parse2(new_expr).unwrap();
65 return;
66 }
67 }
68 }
69 }
70 }
71 visit_mut::visit_expr_mut(self, expr);
72 }
73}
74
75fn extract_and_process_variables(template: &str) -> Vec<(String, String, Option<String>)> {
76 let re = Regex::new(r"\{([^:}]+)(:[^}]*)?}").unwrap();
78 let mut transformations = Vec::new();
79 for cap in re.captures_iter(template) {
80 if let Some(full_match) = cap.get(0) {
81 if let Some(expr_match) = cap.get(1) {
82 let original = full_match.as_str().to_string();
83 let expr_str = expr_match.as_str().trim().to_string();
84 let format_spec = cap.get(2).map(|m| m.as_str().to_string());
85 if !transformations.iter().any(|(orig, _, _)| orig == &original) {
87 transformations.push((original, expr_str, format_spec));
88 }
89 }
90 }
91 }
92 transformations
93}
94
95#[proc_macro_attribute]
96pub fn gui(_args: TokenStream, input: TokenStream) -> TokenStream {
97 let mut input_fn = parse_macro_input!(input as ItemFn);
98 FStringTransformer.visit_item_fn_mut(&mut input_fn);
99 let fn_name = &input_fn.sig.ident;
100 let fn_block = &input_fn.block;
101 let expanded = quote! {
102 #[allow(unused_variables)]
103 fn #fn_name() {
104 fn pyrs_format_json_clean(debug_str: &str) -> String {
105 use regex::Regex;
106 lazy_static::lazy_static! {
107 static ref SIMPLE_VALUES: Regex = Regex::new(r"^\s*(\d+,?\s*)+$").unwrap();
108 }
109 let lines: Vec<&str> = debug_str.lines().collect();
110 let mut result = Vec::new();
111 let mut i = 0;
112 while i < lines.len() {
113 let line = lines[i];
114 let trimmed = line.trim();
115 if SIMPLE_VALUES.is_match(trimmed) && i + 1 < lines.len() {
116 let next_line = lines[i + 1].trim();
117 if SIMPLE_VALUES.is_match(next_line) {
118 let indent = " ".repeat(line.len() - trimmed.len());
119 let combined = format!("{}[{} {}]", indent,
120 trimmed.replace(",", "").trim(),
121 next_line.replace(",", "").trim());
122 result.push(combined);
123 i += 2;
124 continue;
125 }
126 }
127 let cleaned_line = line.replace(",", "");
128 result.push(cleaned_line);
129 i += 1;
130 }
131 result.join("\n")
132 }
133 fn println<T: std::fmt::Display>(text: T) { pyrs::print::println_str(text); }
134 fn print<T: std::fmt::Display>(text: T) { pyrs::print::print_str(text); }
135 use pyrs::input::input_with_validation as input;
136 use pyrs::latex::{latex, latex_display, latex_inline};
137 pyrs::gui::start_gui_server(|| { #fn_block });
138 }
139 };
140 TokenStream::from(expanded)
141}