use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
pub fn is_string_literal(lit: &proc_macro2::Literal) -> bool {
let s = lit.to_string();
s.starts_with('"') || s.starts_with('\'') || s.starts_with("r\"") || s.starts_with("r#")
}
pub fn is_backtick_template(lit: &proc_macro2::Literal) -> bool {
let s = lit.to_string();
if s.starts_with("\"'^") && s.ends_with("^'\"") && s.len() >= 6 {
return true;
}
if s.starts_with("r\"'^") && s.ends_with("^'\"") {
return true;
}
if s.starts_with("r#\"'^") && s.ends_with("^'\"#") {
return true;
}
false
}
pub fn process_backtick_template(lit: &proc_macro2::Literal) -> TokenStream2 {
let raw = lit.to_string();
let content = if raw.starts_with("\"'^") && raw.ends_with("^'\"") {
&raw[3..raw.len() - 3]
} else if raw.starts_with("r\"'^") && raw.ends_with("^'\"") {
&raw[4..raw.len() - 3]
} else if raw.starts_with("r#\"'^") && raw.ends_with("^'\"#") {
&raw[5..raw.len() - 4]
} else {
return quote! { __out.push_str(#raw); };
};
if !content.contains('@') {
let mut output = TokenStream2::new();
output.extend(quote! { __out.push_str("`"); });
output.extend(quote! { __out.push_str(#content); });
output.extend(quote! { __out.push_str("`"); });
return output;
}
let mut output = TokenStream2::new();
output.extend(quote! { __out.push_str("`"); });
let mut chars = content.chars().peekable();
let mut current_literal = String::new();
while let Some(c) = chars.next() {
if c == '@' {
match chars.peek() {
Some(&'@') => {
chars.next(); current_literal.push('@');
}
Some(&'{') => {
if !current_literal.is_empty() {
output.extend(quote! { __out.push_str(#current_literal); });
current_literal.clear();
}
chars.next();
let mut expr_str = String::new();
let mut brace_depth = 1;
for ec in chars.by_ref() {
if ec == '{' {
brace_depth += 1;
expr_str.push(ec);
} else if ec == '}' {
brace_depth -= 1;
if brace_depth == 0 {
break;
}
expr_str.push(ec);
} else {
expr_str.push(ec);
}
}
if let Ok(expr) = syn::parse_str::<syn::Expr>(&expr_str) {
output.extend(quote! {
__out.push_str(¯oforge_ts::ts_syn::ToTsString::to_ts_string(&#expr));
});
} else {
let fallback = format!("@{{{}}}", expr_str);
output.extend(quote! { __out.push_str(#fallback); });
}
}
_ => {
current_literal.push('@');
}
}
} else {
current_literal.push(c);
}
}
if !current_literal.is_empty() {
output.extend(quote! { __out.push_str(#current_literal); });
}
output.extend(quote! { __out.push_str("`"); });
output
}
pub fn interpolate_string_literal(lit: &proc_macro2::Literal) -> TokenStream2 {
let raw = lit.to_string();
let (quote_char, content) = if raw.starts_with('"') {
('"', &raw[1..raw.len() - 1])
} else if raw.starts_with('\'') {
('\'', &raw[1..raw.len() - 1])
} else if raw.starts_with("r\"") {
('"', &raw[2..raw.len() - 1])
} else if raw.starts_with("r#") {
let hash_count = raw[1..].chars().take_while(|&c| c == '#').count();
let start = 2 + hash_count; let end = raw.len() - 1 - hash_count; ('"', &raw[start..end])
} else {
return quote! { __out.push_str(#raw); };
};
if !content.contains('@') {
return quote! { __out.push_str(#raw); };
}
let mut output = TokenStream2::new();
let quote_str = quote_char.to_string();
output.extend(quote! { __out.push_str(#quote_str); });
let mut chars = content.chars().peekable();
let mut current_literal = String::new();
while let Some(c) = chars.next() {
if c == '@' {
match chars.peek() {
Some(&'@') => {
chars.next(); current_literal.push('@');
}
Some(&'{') => {
if !current_literal.is_empty() {
output.extend(quote! { __out.push_str(#current_literal); });
current_literal.clear();
}
chars.next();
let mut expr_str = String::new();
let mut brace_depth = 1;
for ec in chars.by_ref() {
if ec == '{' {
brace_depth += 1;
expr_str.push(ec);
} else if ec == '}' {
brace_depth -= 1;
if brace_depth == 0 {
break;
}
expr_str.push(ec);
} else {
expr_str.push(ec);
}
}
if let Ok(expr) = syn::parse_str::<syn::Expr>(&expr_str) {
output.extend(quote! {
__out.push_str(¯oforge_ts::ts_syn::ToTsString::to_ts_string(&#expr));
});
} else {
let fallback = format!("@{{{}}}", expr_str);
output.extend(quote! { __out.push_str(#fallback); });
}
}
_ => {
current_literal.push('@');
}
}
} else if c == '\\' {
current_literal.push(c);
if chars.peek().is_some() {
current_literal.push(chars.next().unwrap());
}
} else {
current_literal.push(c);
}
}
if !current_literal.is_empty() {
output.extend(quote! { __out.push_str(#current_literal); });
}
output.extend(quote! { __out.push_str(#quote_str); });
output
}