use super::Transpiler;
use crate::frontend::ast::{Expr, ExprKind, Literal};
use anyhow::Result;
use proc_macro2::TokenStream;
use quote::quote;
impl Transpiler {
pub fn transpile_print_with_interpolation(
&self,
func_name: &str,
parts: &[crate::frontend::ast::StringPart],
) -> Result<TokenStream> {
if parts.is_empty() {
let func_tokens = proc_macro2::Ident::new(func_name, proc_macro2::Span::call_site());
return Ok(quote! { #func_tokens!("") });
}
let mut format_string = String::new();
let mut args = Vec::new();
for part in parts {
match part {
crate::frontend::ast::StringPart::Text(s) => {
format_string.push_str(&s.replace('{', "{{").replace('}', "}}"));
}
crate::frontend::ast::StringPart::Expr(expr) => {
format_string.push_str("{}");
let expr_tokens = self.transpile_expr(expr)?;
args.push(expr_tokens);
}
crate::frontend::ast::StringPart::ExprWithFormat { expr, format_spec } => {
format_string.push('{');
format_string.push_str(format_spec);
format_string.push('}');
let expr_tokens = self.transpile_expr(expr)?;
args.push(expr_tokens);
}
}
}
let func_tokens = proc_macro2::Ident::new(func_name, proc_macro2::Span::call_site());
Ok(quote! {
#func_tokens!(#format_string #(, #args)*)
})
}
pub fn try_transpile_print_macro(
&self,
func_tokens: &TokenStream,
base_name: &str,
args: &[Expr],
) -> Result<Option<TokenStream>> {
if !(base_name == "println"
|| base_name == "print"
|| base_name == "dbg"
|| base_name == "panic")
{
return Ok(None);
}
if (base_name == "println" || base_name == "print") && args.len() == 1 {
if let ExprKind::StringInterpolation { parts } = &args[0].kind {
return Ok(Some(
self.transpile_print_with_interpolation(base_name, parts)?,
));
}
if !matches!(&args[0].kind, ExprKind::Literal(Literal::String(_))) {
let arg_tokens = self.transpile_expr(&args[0])?;
let format_str = "{:?}";
return Ok(Some(quote! { #func_tokens!(#format_str, #arg_tokens) }));
}
}
if args.len() > 1 {
return self.transpile_print_multiple_args(func_tokens, args);
}
if args.len() == 1 {
if let ExprKind::Literal(Literal::String(s)) = &args[0].kind {
let escaped = s.replace('{', "{{").replace('}', "}}");
return Ok(Some(quote! { #func_tokens!(#escaped) }));
}
}
let arg_tokens: Result<Vec<_>> = args.iter().map(|a| self.transpile_expr(a)).collect();
let arg_tokens = arg_tokens?;
Ok(Some(quote! { #func_tokens!(#(#arg_tokens),*) }))
}
pub fn transpile_print_multiple_args(
&self,
func_tokens: &TokenStream,
args: &[Expr],
) -> Result<Option<TokenStream>> {
if args.is_empty() {
return Ok(Some(quote! { #func_tokens!() }));
}
let all_args: Result<Vec<_>> = args.iter().map(|a| self.transpile_expr(a)).collect();
let all_args = all_args?;
if args.len() == 1 {
match &args[0].kind {
ExprKind::Literal(Literal::String(_)) | ExprKind::StringInterpolation { .. } => {
Ok(Some(quote! { #func_tokens!("{}", #(#all_args)*) }))
}
ExprKind::Identifier(_) => {
let arg = &all_args[0];
let printing_logic = self
.generate_value_printing_tokens(quote! { #arg }, quote! { #func_tokens });
Ok(Some(printing_logic))
}
_ => {
Ok(Some(quote! { #func_tokens!("{:?}", #(#all_args)*) }))
}
}
} else {
if let ExprKind::Literal(Literal::String(format_str)) = &args[0].kind {
if format_str.contains("{}") {
let format_arg = &all_args[0];
let value_args = &all_args[1..];
Ok(Some(
quote! { #func_tokens!(#format_arg, #(#value_args),*) },
))
} else {
let format_parts: Vec<_> = args
.iter()
.map(|arg| match &arg.kind {
ExprKind::Literal(Literal::String(_)) => "{}",
_ => "{:?}",
})
.collect();
let format_str = format_parts.join(" ");
Ok(Some(quote! { #func_tokens!(#format_str, #(#all_args),*) }))
}
} else {
let format_parts: Vec<_> = args
.iter()
.map(|arg| match &arg.kind {
ExprKind::Literal(Literal::String(_)) => "{}",
_ => "{:?}",
})
.collect();
let format_str = format_parts.join(" ");
Ok(Some(quote! { #func_tokens!(#format_str, #(#all_args),*) }))
}
}
}
pub fn generate_value_printing_tokens(
&self,
value_expr: TokenStream,
func_tokens: TokenStream,
) -> TokenStream {
quote! {
{
use std::any::Any;
let value = #value_expr;
if let Some(s) = (&value as &dyn Any).downcast_ref::<String>() {
#func_tokens!("{}", s)
} else if let Some(s) = (&value as &dyn Any).downcast_ref::<&str>() {
#func_tokens!("{}", s)
} else {
#func_tokens!("{:?}", value)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::{Literal, Span, StringPart};
fn make_expr(kind: ExprKind) -> Expr {
Expr {
kind,
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn string_expr(s: &str) -> Expr {
make_expr(ExprKind::Literal(Literal::String(s.to_string())))
}
fn int_expr(n: i64) -> Expr {
make_expr(ExprKind::Literal(Literal::Integer(n, None)))
}
fn ident_expr(name: &str) -> Expr {
make_expr(ExprKind::Identifier(name.to_string()))
}
fn interpolation_expr(parts: Vec<StringPart>) -> Expr {
make_expr(ExprKind::StringInterpolation { parts })
}
#[test]
fn test_print_interpolation_empty() {
let transpiler = Transpiler::new();
let parts: Vec<StringPart> = vec![];
let result = transpiler.transpile_print_with_interpolation("println", &parts);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("println"));
}
#[test]
fn test_print_interpolation_text_only() {
let transpiler = Transpiler::new();
let parts = vec![StringPart::Text("Hello, World!".to_string())];
let result = transpiler.transpile_print_with_interpolation("println", &parts);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("println"));
assert!(tokens_str.contains("Hello, World!"));
}
#[test]
fn test_print_interpolation_expr() {
let transpiler = Transpiler::new();
let parts = vec![
StringPart::Text("Value: ".to_string()),
StringPart::Expr(Box::new(int_expr(42))),
];
let result = transpiler.transpile_print_with_interpolation("println", &parts);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("println"));
assert!(tokens_str.contains("Value:"));
assert!(tokens_str.contains("42"));
}
#[test]
fn test_print_interpolation_with_format_spec() {
let transpiler = Transpiler::new();
let parts = vec![StringPart::ExprWithFormat {
expr: Box::new(int_expr(42)),
format_spec: ":>10".to_string(),
}];
let result = transpiler.transpile_print_with_interpolation("print", &parts);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("print"));
}
#[test]
fn test_print_interpolation_escapes_braces() {
let transpiler = Transpiler::new();
let parts = vec![StringPart::Text("Use {braces}".to_string())];
let result = transpiler.transpile_print_with_interpolation("println", &parts);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("{{braces}}"));
}
#[test]
fn test_print_interpolation_multiple_parts() {
let transpiler = Transpiler::new();
let parts = vec![
StringPart::Text("Hello, ".to_string()),
StringPart::Expr(Box::new(ident_expr("name"))),
StringPart::Text("! You have ".to_string()),
StringPart::Expr(Box::new(int_expr(5))),
StringPart::Text(" messages.".to_string()),
];
let result = transpiler.transpile_print_with_interpolation("println", &parts);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("println"));
assert!(tokens_str.contains("name"));
}
#[test]
fn test_print_macro_println() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![string_expr("Hello")];
let result = transpiler.try_transpile_print_macro(&func_tokens, "println", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("println"));
}
#[test]
fn test_print_macro_println_escapes_braces() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![string_expr("Delimiters: {, }")];
let result = transpiler.try_transpile_print_macro(&func_tokens, "println", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(
tokens_str.contains("{{") && tokens_str.contains("}}"),
"Expected escaped braces, got: {}",
tokens_str
);
}
#[test]
fn test_print_macro_print() {
let transpiler = Transpiler::new();
let func_tokens = quote! { print };
let args = vec![string_expr("Hello")];
let result = transpiler.try_transpile_print_macro(&func_tokens, "print", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_macro_dbg() {
let transpiler = Transpiler::new();
let func_tokens = quote! { dbg };
let args = vec![int_expr(42)];
let result = transpiler.try_transpile_print_macro(&func_tokens, "dbg", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_macro_panic() {
let transpiler = Transpiler::new();
let func_tokens = quote! { panic };
let args = vec![string_expr("error!")];
let result = transpiler.try_transpile_print_macro(&func_tokens, "panic", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_macro_unknown() {
let transpiler = Transpiler::new();
let func_tokens = quote! { foo };
let args = vec![int_expr(42)];
let result = transpiler.try_transpile_print_macro(&func_tokens, "foo", &args);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_print_macro_with_interpolation() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![interpolation_expr(vec![
StringPart::Text("Hello, ".to_string()),
StringPart::Expr(Box::new(ident_expr("name"))),
])];
let result = transpiler.try_transpile_print_macro(&func_tokens, "println", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_macro_single_int() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![int_expr(42)];
let result = transpiler.try_transpile_print_macro(&func_tokens, "println", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("{:?}"));
}
#[test]
fn test_print_multiple_args_empty() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args: Vec<Expr> = vec![];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_multiple_args_single_string() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![string_expr("Hello")];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("{}"));
}
#[test]
fn test_print_multiple_args_single_ident() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![ident_expr("value")];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_multiple_args_single_int() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![int_expr(42)];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("{:?}"));
}
#[test]
fn test_print_multiple_args_format_string() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![string_expr("Value: {}"), int_expr(42)];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_multiple_args_no_format_string() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![string_expr("Hello"), string_expr("World")];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_multiple_args_mixed_types() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![string_expr("Count:"), int_expr(42), ident_expr("name")];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_print_multiple_args_no_format_all_ints() {
let transpiler = Transpiler::new();
let func_tokens = quote! { println };
let args = vec![int_expr(1), int_expr(2), int_expr(3)];
let result = transpiler.transpile_print_multiple_args(&func_tokens, &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("{:?}"));
}
#[test]
fn test_generate_value_printing_tokens() {
let transpiler = Transpiler::new();
let value_expr = quote! { my_value };
let func_tokens = quote! { println };
let result = transpiler.generate_value_printing_tokens(value_expr, func_tokens);
let result_str = result.to_string();
assert!(result_str.contains("Any"));
assert!(result_str.contains("downcast_ref"));
assert!(result_str.contains("String"));
}
#[test]
fn test_generate_value_printing_tokens_different_func() {
let transpiler = Transpiler::new();
let value_expr = quote! { x };
let func_tokens = quote! { print };
let result = transpiler.generate_value_printing_tokens(value_expr, func_tokens);
let result_str = result.to_string();
assert!(result_str.contains("print"));
}
#[test]
fn test_generate_value_printing_tokens_complex_expr() {
let transpiler = Transpiler::new();
let value_expr = quote! { some_struct.field };
let func_tokens = quote! { println };
let result = transpiler.generate_value_printing_tokens(value_expr, func_tokens);
let result_str = result.to_string();
assert!(result_str.contains("some_struct"));
}
}