use proc_macro2::TokenStream;
use quote::quote;
#[derive(Debug, Clone)]
pub enum Segment {
Literal(String),
ValueInterpolation(TokenStream),
RawInterpolation(TokenStream),
}
pub fn parse_interpolations(source: &str) -> (Vec<Segment>, bool) {
let mut segments = Vec::new();
let mut has_interpolation = false;
let mut chars = source.chars().peekable();
let mut current_literal = String::new();
while let Some(c) = chars.next() {
if c == '#' && chars.peek() == Some(&'{') {
chars.next();
if !current_literal.is_empty() {
segments.push(Segment::Literal(current_literal.clone()));
current_literal.clear();
}
if let Some((_expr_str, tokens)) = parse_interpolation_expr(&mut chars) {
segments.push(Segment::ValueInterpolation(tokens));
has_interpolation = true;
} else {
current_literal.push_str("#{");
}
}
else if c == '@' && chars.peek() == Some(&'{') {
chars.next();
if !current_literal.is_empty() {
segments.push(Segment::Literal(current_literal.clone()));
current_literal.clear();
}
if let Some((_expr_str, tokens)) = parse_interpolation_expr(&mut chars) {
segments.push(Segment::RawInterpolation(tokens));
has_interpolation = true;
} else {
current_literal.push_str("@{");
}
} else {
current_literal.push(c);
}
}
if !current_literal.is_empty() {
segments.push(Segment::Literal(current_literal));
}
(segments, has_interpolation)
}
fn parse_interpolation_expr(
chars: &mut std::iter::Peekable<std::str::Chars>,
) -> Option<(String, TokenStream)> {
let mut depth = 1;
let mut expr_str = String::new();
for c in chars.by_ref() {
if c == '{' {
depth += 1;
expr_str.push(c);
} else if c == '}' {
depth -= 1;
if depth == 0 {
break;
}
expr_str.push(c);
} else {
expr_str.push(c);
}
}
match expr_str.parse::<TokenStream>() {
Ok(tokens) => Some((expr_str, tokens)),
Err(_) => None,
}
}
pub fn generate_interpolated_code(segments: &[Segment], has_interpolation: bool) -> TokenStream {
if has_interpolation {
let mut format_str = String::new();
let mut args: Vec<TokenStream> = Vec::new();
for segment in segments {
match segment {
Segment::Literal(lit) => {
format_str.push_str(&lit.replace('{', "{{").replace('}', "}}"));
}
Segment::ValueInterpolation(expr) => {
format_str.push_str("{}");
args.push(quote! { ::viewpoint_js_core::ToJsValue::to_js_value(&(#expr)) });
}
Segment::RawInterpolation(expr) => {
format_str.push_str("{}");
args.push(quote! { (#expr).as_ref() as &str });
}
}
}
quote! {
{
format!(#format_str, #(#args),*)
}
}
} else {
let js_str = segments
.iter()
.filter_map(|s| match s {
Segment::Literal(lit) => Some(lit.as_str()),
Segment::ValueInterpolation(_) | Segment::RawInterpolation(_) => None,
})
.collect::<String>();
quote! { #js_str }
}
}
#[cfg(test)]
mod tests;