mod control_flow;
mod interpolation;
mod parser;
mod spacing;
mod tag;
use proc_macro2::{Delimiter, TokenStream as TokenStream2, TokenTree};
use quote::quote;
use parser::parse_fragment;
pub fn compile_template(input: TokenStream2) -> syn::Result<TokenStream2> {
let parsed = parse_position(input)?;
let position = parsed.position;
let (body, _) = parse_fragment(&mut parsed.body.into_iter().peekable(), None)?;
let insert_pos = position_to_tokens(position);
let output = if position == Some("Within") {
quote! {
{
let mut __out = String::new();
let mut __patches: Vec<macroforge_ts::ts_syn::abi::Patch> = Vec::new();
__out.push_str("/* @macroforge:body */");
#body
macroforge_ts::ts_syn::TsStream::with_insert_pos_and_patches(__out, #insert_pos, __patches)
}
}
} else {
quote! {
{
let mut __out = String::new();
let mut __patches: Vec<macroforge_ts::ts_syn::abi::Patch> = Vec::new();
#body
macroforge_ts::ts_syn::TsStream::with_insert_pos_and_patches(__out, #insert_pos, __patches)
}
}
};
Ok(output)
}
struct ParsedInput {
position: Option<&'static str>,
body: TokenStream2,
}
fn parse_position(input: TokenStream2) -> syn::Result<ParsedInput> {
let mut iter = input.clone().into_iter().peekable();
if let Some(TokenTree::Ident(ident)) = iter.peek() {
let pos = match ident.to_string().as_str() {
"Top" => Some("Top"),
"Above" => Some("Above"),
"Within" => Some("Within"),
"Below" => Some("Below"),
"Bottom" => Some("Bottom"),
_ => None,
};
if pos.is_some() {
iter.next();
let remaining: TokenStream2 = iter.collect();
let mut remaining_iter = remaining.into_iter();
if let Some(TokenTree::Group(group)) = remaining_iter.next()
&& group.delimiter() == Delimiter::Brace
{
return Ok(ParsedInput {
position: pos,
body: group.stream(),
});
}
return Err(syn::Error::new_spanned(
input,
"expected `{` after position keyword (e.g., `ts_template!(Within { ... })`)",
));
}
}
Ok(ParsedInput {
position: None,
body: input,
})
}
fn position_to_tokens(position: Option<&str>) -> TokenStream2 {
match position {
Some("Top") => quote! { macroforge_ts::ts_syn::InsertPos::Top },
Some("Above") => quote! { macroforge_ts::ts_syn::InsertPos::Above },
Some("Within") => quote! { macroforge_ts::ts_syn::InsertPos::Within },
Some("Bottom") => quote! { macroforge_ts::ts_syn::InsertPos::Bottom },
_ => quote! { macroforge_ts::ts_syn::InsertPos::Below },
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_interpolation() {
let input: TokenStream2 = "const x = @{value};".parse().unwrap();
let result = compile_template(input);
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
}
#[test]
fn test_with_position() {
use quote::quote;
let input = quote! {
Within {
debug() { return "test"; }
}
};
let result = compile_template(input);
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
}
}