tour-parser 0.1.0

Tour Template Parser
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::*;
use tour_core::Delimiter;

use crate::{ast::*, common::{TemplDisplay, INNER_BLOCK}, data::Template, file::AliasKind, syntax::*};

use super::brace;

pub struct Visitor<'a> {
    tokens: &'a mut TokenStream,
    static_len: usize,
}

struct Shared<'a> {
    templ: &'a Template,
    input: &'a DeriveInput,
}

impl<'a> Visitor<'a> {
    pub fn generate(templ: &'a Template, input: &'a DeriveInput, tokens: &'a mut TokenStream) {
        let mut me = Self { tokens, static_len: 0, };
        let shared = Shared { templ, input };
        me.gens(templ.stmts(), &shared);
    }

    pub fn generate_block(templ: &'a Template, block: &Ident, input: &'a DeriveInput, tokens: &'a mut TokenStream) {
        let mut me = Self { tokens, static_len: 0, };
        let shared = Shared { templ, input };
        me.gens(&templ.file().block(block).stmts, &shared);
    }

    fn gens(&mut self, stmts: &[StmtTempl], shared: &Shared) {
        self.gen_destructure(shared);
        self.gen_sources(shared);
        self.visit_stmts(stmts, shared);
        self.tokens.extend(quote! {
            Ok(())
        });
    }

    fn gen_destructure(&mut self, shared: &Shared) {
        match &shared.input.data {
            Data::Struct(data) if matches!(data.fields, Fields::Named(_)) => {
                let ty = &shared.input.ident;
                self.tokens.extend(quote! { let #ty });

                brace(self.tokens, |tokens| {
                    for field in &data.fields {
                        field.ident.as_ref().expect("named").to_tokens(tokens);
                        <Token![,]>::default().to_tokens(tokens);
                    }
                });

                self.tokens.extend(quote! { = self; });
            }
            _ => {}
        }
    }

    fn gen_sources(&mut self, shared: &Shared) {
        let meta = shared.templ.meta();
        let path = meta.path();
        let statics = shared.templ.file().statics();
        match (meta.is_file(), meta.reload().as_bool()) {
            (true,Ok(true)) => self.tokens.extend(quote!{
                let sources = ::std::fs::read_to_string(#path)?;
                let sources = ::tour::Parser::new(&sources, ::tour::StaticVisitor::new())
                    .parse()?.statics;
            }),
            (true,Ok(false)) | (false,Ok(false)) => {}
            (true, Err(cond)) => self.tokens.extend(quote! {
                let sources = if #cond {
                    let sources = ::std::fs::read_to_string(#path)?;
                    ::tour::Parser::new(&sources, ::tour::StaticVisitor::new())
                        .parse()?.statics
                } else {
                    vec![]
                };
            }),
            (false, _) if statics.is_empty() => {}
            (false, _) => self.tokens.extend(quote! {
                let sources = [#(#statics),*];
            }),
        }
    }

    fn visit_stmts(&mut self, stmts: &[StmtTempl], shared: &Shared) {
        for stmt in stmts {
            self.visit_stmt(stmt, shared);
        }
    }

    fn visit_stmt(&mut self, stmt: &StmtTempl, shared: &Shared) {
        match stmt {
            StmtTempl::Scalar(scalar) => match scalar {
                Scalar::Static { value, index } => {
                    let idx = Index::from(*index as usize);

                    match shared.templ.meta().reload().as_bool() {
                        Ok(true) => self.tokens.extend(quote! {
                            #TemplDisplay::display(&sources[#idx], writer)?;
                        }),
                        Ok(false) => self.tokens.extend(quote! {
                            #TemplDisplay::display(&#value, writer)?;
                        }),
                        Err(expr) => self.tokens.extend(quote! {
                            #TemplDisplay::display(if #expr { &sources[#idx] } else { #value }, writer)?;
                        }),
                    }

                    self.static_len += 1;
                },
                Scalar::Yield(YieldTempl { block, .. }) => {
                    let block = block.as_ref().map(|e|e.1.to_string()).unwrap_or(INNER_BLOCK.into());
                    self.tokens.extend(quote! {
                        ::tour::Template::render_block_into(self.0, #block, writer)?;
                    });
                },
                Scalar::Render(RenderTempl { value: RenderValue::Ident(id), block, .. }) => {
                    match (shared.templ.file().resolve_id(id), block) {
                        (AliasKind::Block(block), None) => {
                            self.visit_stmts(&block.stmts, shared)
                        },
                        (AliasKind::Block(_), Some(_)) => unreachable!("cannot render block from block"),
                        (AliasKind::Import(import), None) => {
                            let name = import.alias();
                            self.tokens.extend(quote! {
                                ::tour::Template::render_into(&#name(self), &mut *writer)?;
                            });
                        },
                        (AliasKind::Import(import), Some((_, block))) => {
                            let name = import.alias();
                            let id = block.to_string();
                            self.tokens.extend(quote! {
                                ::tour::Template::render_block_into(&#name(self), #id, &mut *writer)?;
                            });
                        },
                    }
                },
                Scalar::Render(RenderTempl { value: RenderValue::Path(path), block, .. }) => {
                    match block {
                        Some((_, block)) => {
                            let import = shared.templ.file().import_by_path(path);
                            let name = import.alias();
                            let id = block.to_string();
                            self.tokens.extend(quote! {
                                ::tour::Template::render_block_into(&#name(self), #id, &mut *writer)?;
                            });
                        },
                        None => {
                            let import = shared.templ.file().import_by_path(path);
                            let name = import.alias();
                            self.tokens.extend(quote! {
                                ::tour::Template::render_into(&#name(self), &mut *writer)?;
                            });
                        },
                    }
                },
                // Scalar::Render(RenderTempl { value, block, .. }) => match value {
                //     // Either Block, just visit_stmts, or Import Aliased, render by type
                //     RenderValue::Ident(id) => {
                //         match shared.templ.file().resolve_id(id) {
                //             AliasKind::Block(block) => self.visit_stmts(&block.stmts, shared),
                //             AliasKind::Import(import) => {
                //                 let name = &import.alias();
                //                 self.tokens.extend(quote! {
                //                     ::tour::Template::render_into(&#name(self), &mut *writer)?;
                //                 });
                //             }
                //         }
                //     },
                //     // Import directly, just render by type
                //     RenderValue::Path(path) => {
                //     },
                // },
                Scalar::Expr { expr, delim } => {
                    let display = display(*delim, expr);
                    let writer = writer(*delim);
                    self.tokens.extend(quote! {
                        #TemplDisplay::display(#display, #writer)?;
                    });
                },
                Scalar::Use(_) => unreachable!("use alias statement should be discarded"),
                Scalar::Item(item) => match item.as_ref() {
                    ItemTempl::Use(item) => item.to_tokens(self.tokens),
                    ItemTempl::Const(item) => item.to_tokens(self.tokens),
                },
            },
            StmtTempl::Scope(scope) => self.visit_scope(scope, shared),
        }
    }

    fn visit_scope(&mut self, scope: &Scope, shared: &Shared) {
        match scope {
            Scope::Root { stmts } => {
                token::Brace::default()
                    .surround(self.tokens, |tokens|{
                        let mut visitor = Visitor { tokens, static_len: self.static_len  };
                        visitor.visit_stmts(stmts, shared);
                        self.static_len = visitor.static_len;
                    });
            },
            Scope::If { templ, stmts, else_branch } => {
                templ.if_token.to_tokens(self.tokens);
                templ.cond.to_tokens(self.tokens);
                token::Brace::default()
                    .surround(self.tokens, |tokens|{
                        let mut visitor = Visitor { tokens, static_len: self.static_len  };
                        visitor.visit_stmts(stmts, shared);
                        self.static_len = visitor.static_len;
                    });

                if let Some((else_token, else_scope)) = else_branch {
                    else_token.to_tokens(self.tokens);
                    self.visit_scope(else_scope, shared);
                }
            },
            Scope::For { templ, stmts, else_branch } => {
                let expr = &templ.expr;

                self.tokens.extend(quote! {
                    let __for_expr = #expr;
                });

                templ.for_token.to_tokens(self.tokens);
                templ.pat.to_tokens(self.tokens);
                templ.in_token.to_tokens(self.tokens);
                format_ident!("__for_expr").to_tokens(self.tokens);

                token::Brace::default()
                    .surround(self.tokens, |tokens|{
                        let mut visitor = Visitor { tokens, static_len: self.static_len  };
                        visitor.visit_stmts(stmts, shared);
                        self.static_len = visitor.static_len;
                    });

                if let Some((_, else_scope)) = else_branch {
                    self.tokens.extend(quote! {
                        if ExactSizeIterator::len(&IntoIterator::into_iter(__for_expr)) == 0
                    });

                    self.visit_scope(else_scope, shared);
                }
            },
            Scope::Block { .. } => unreachable!("`block` scope should be replaced with `render`")
        }
    }
}

fn display(delim: Delimiter, expr: &syn::Expr) -> TokenStream {
    use Delimiter::*;

    match delim {
        Quest => quote! {&::tour::Debug(&#expr)},
        Percent => quote! {&::tour::Display(&#expr)},
        Brace | Bang | Hash => quote! {&#expr},
    }
}

fn writer(delim: Delimiter) -> TokenStream {
    use Delimiter::*;

    match delim {
        Bang => quote! {&mut *writer},
        Brace | Percent | Quest | Hash => quote! {&mut ::tour::Escape(&mut *writer)},
    }
}