use crate::data::{DataGroup, Query};
use crate::expression::FieldExpression;
use crate::{RowTemplate, RowTemplateKind, kw};
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use syn::Token;
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream};
#[derive(Debug)]
pub struct TemplateAst(Vec<TemplatedTokenTree>);
#[derive(Debug)]
pub enum TemplatedTokenTree {
Group(TemplatedGroup),
Ident(proc_macro2::Ident),
Punct(proc_macro2::Punct),
Literal(proc_macro2::Literal),
Replacement(Replacement),
Error(syn::Error),
}
#[derive(Debug)]
pub struct TemplatedGroup {
delimiter: Delimiter,
stream: TemplateAst,
}
#[derive(Debug)]
pub enum Replacement {
Expression(FieldExpression),
Group(RowTemplate),
}
impl TemplateAst {
pub(crate) fn render(
&self,
data_source: Option<&DataGroup>,
errors: &mut TokenStream,
) -> TokenStream {
let mut output = TokenStream::new();
for tree in &self.0 {
match tree {
TemplatedTokenTree::Group(group) => output.extend([TokenTree::Group(
group.to_group_for_data(data_source, errors),
)]),
TemplatedTokenTree::Ident(ident) => {
output.extend([TokenTree::Ident(ident.clone())])
}
TemplatedTokenTree::Punct(punct) => {
output.extend([TokenTree::Punct(punct.clone())])
}
TemplatedTokenTree::Literal(literal) => {
output.extend([TokenTree::Literal(literal.clone())])
}
TemplatedTokenTree::Replacement(replacement) => {
output.extend(replacement.render(data_source, errors))
}
TemplatedTokenTree::Error(error) => errors.extend(error.to_compile_error()),
}
}
output
}
}
impl TemplatedGroup {
pub(crate) fn to_group_for_data(
&self,
data_source: Option<&DataGroup>,
errors: &mut TokenStream,
) -> Group {
Group::new(self.delimiter, self.stream.render(data_source, errors))
}
}
impl Replacement {
pub(crate) fn render(
&self,
data_source: Option<&DataGroup>,
errors: &mut TokenStream,
) -> TokenStream {
match self {
Replacement::Expression(expr) => expr.render(data_source),
Replacement::Group(group) => group.render(data_source, errors),
}
}
}
impl TemplatedGroup {
fn new(delimiter: Delimiter, token_stream: TokenStream) -> TemplatedGroup {
Self {
delimiter,
stream: syn::parse2(token_stream).expect("TTS parse never fails"),
}
}
}
fn try_parse_replacement<'a>(
hash: &TokenTree,
cursor: Cursor<'a>,
) -> Option<syn::Result<(Replacement, Cursor<'a>)>> {
let (second, rest) = cursor.token_tree()?;
match &second {
TokenTree::Ident(ident) if ident == "each" || ident == "find" || ident == "having" => {
let keyword = second;
let mut tokens = vec![hash.clone(), keyword];
let (third_token, rest) = rest.token_tree()?;
let rest = if let TokenTree::Group(group) = &third_token {
if group.delimiter() == Delimiter::Parenthesis {
tokens.push(third_token);
let (fourth_token, rest) = rest.token_tree()?;
tokens.push(fourth_token);
rest
} else {
tokens.push(third_token);
rest
}
} else {
return None; };
if let Some((else_hash, rest)) = rest.token_tree()
&& let TokenTree::Punct(p) = &else_hash
&& p.as_char() == '#'
{
let (r#else, rest) = rest.token_tree()?;
if &r#else.to_string() == "else" {
let (else_template_braces, rest) = rest.token_tree()?;
tokens.extend([else_hash, r#else, else_template_braces]);
let result = syn::parse2(tokens.into_iter().collect());
return Some(result.map(|replacement| (replacement, rest)));
}
}
let result = syn::parse2(tokens.into_iter().collect());
Some(result.map(|replacement| (replacement, rest)))
}
TokenTree::Ident(_ident) => {
let ident = second;
let (template_parens, rest) = rest.token_tree()?;
let result = syn::parse2([hash.clone(), ident, template_parens].into_iter().collect());
Some(result.map(|replacement| (replacement, rest)))
}
TokenTree::Group(group) if group.delimiter() == Delimiter::Parenthesis => {
let result = syn::parse2([hash.clone(), second].into_iter().collect());
Some(result.map(|replacement| (replacement, rest)))
}
TokenTree::Literal(_literal) => {
let result = syn::parse2([hash.clone(), second].into_iter().collect());
Some(result.map(|replacement| (replacement, rest)))
}
_ => None,
}
}
impl Parse for TemplateAst {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut errors = vec![];
let mut templated = vec![];
input
.step(|cursor| {
let mut rest = *cursor;
while let Some((tt, next)) = rest.token_tree() {
if let TokenTree::Punct(p) = &tt
&& p.as_char() == '#'
&& let Some(result) = try_parse_replacement(&tt, next)
{
match result {
Ok((replacement, cursor)) => {
rest = cursor;
templated.push(TemplatedTokenTree::Replacement(replacement));
continue;
}
Err(error) => {
errors.push(error);
}
}
}
templated.push(tt.into());
rest = next;
}
Ok(((), rest))
})
.expect("No failure case");
for error in errors {
templated.push(TemplatedTokenTree::Error(error));
}
Ok(Self(templated))
}
}
impl From<TokenTree> for TemplatedTokenTree {
fn from(token_tree: TokenTree) -> Self {
match token_tree {
TokenTree::Group(group) => {
Self::Group(TemplatedGroup::new(group.delimiter(), group.stream()))
}
TokenTree::Ident(ident) => Self::Ident(ident),
TokenTree::Punct(punct) => Self::Punct(punct),
TokenTree::Literal(literal) => Self::Literal(literal),
}
}
}
impl Parse for Replacement {
fn parse(input: ParseStream) -> syn::Result<Self> {
assert!(input.peek(Token![#]));
if input.peek2(kw::each) || input.peek2(kw::find) || input.peek2(kw::having) {
Ok(Replacement::Group(input.parse()?))
} else {
Ok(Replacement::Expression(input.parse()?))
}
}
}
impl TemplateAst {
pub(crate) fn to_query(&self) -> Query {
self.0.iter().map(|tree| tree.to_query()).collect()
}
}
impl TemplatedTokenTree {
pub(crate) fn to_query(&self) -> Query {
match self {
TemplatedTokenTree::Group(group) => group.stream.to_query(),
TemplatedTokenTree::Ident(_)
| TemplatedTokenTree::Punct(_)
| TemplatedTokenTree::Literal(_)
| TemplatedTokenTree::Error(_) => Query::default(),
TemplatedTokenTree::Replacement(replacement) => replacement.to_query(),
}
}
}
impl Replacement {
fn to_query(&self) -> Query {
match self {
Replacement::Expression(expr) => expr.to_query(),
Replacement::Group(group) => group.to_query(),
}
}
}
impl RowTemplate {
fn to_query(&self) -> Query {
if let RowTemplateKind::Having(_) = self.kind
&& let Some(filter) = &self.filter
{
Query::any(filter.clone())
} else {
self.else_template
.as_ref()
.map_or_else(Query::default, |et| et.template.to_query())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_snapshot;
#[test]
fn parse_str_replacement() {
let r = syn::parse_str::<Replacement>(r#" #("{field}") "#).unwrap();
let mut errors = TokenStream::new();
let output = r.render(None, &mut errors);
assert_snapshot!(output.to_string(), @r#""__placeholder_string_to_enable_syntax_checking""#);
}
#[test]
fn parse_literal_replacement() {
let r = syn::parse_str::<Replacement>(r#"#({field})"#).unwrap();
let mut errors = TokenStream::new();
let output = r.render(None, &mut errors);
assert_snapshot!(output.to_string(), @r#""__placeholder_string_to_enable_syntax_checking""#);
}
#[test]
fn parse_literal_replacement_in_template_stream() {
let r = syn::parse_str::<TemplateAst>(r#"let #({field});"#).unwrap();
let mut errors = TokenStream::new();
let output = r.render(None, &mut errors);
assert_snapshot!(output.to_string(), @r#"let "__placeholder_string_to_enable_syntax_checking" ;"#);
}
#[test]
fn parse_for_loop_replacement() {
let r = syn::parse_str::<Replacement>(
r#"#each(category == "citrus"){
match fruit_id {
#each{
#({id}) => concat!("Found citrus: ", #("{name}")),
}
_ => "Not a citrus fruit",
}
}"#,
)
.unwrap();
let mut errors = TokenStream::new();
let output = r.render(None, &mut errors);
assert_snapshot!(output.to_string(), @r#"match fruit_id { "__placeholder_string_to_enable_syntax_checking" => concat ! ("Found citrus: " , "__placeholder_string_to_enable_syntax_checking") , _ => "Not a citrus fruit" , }"#);
}
}