use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{parse_quote, token::Brace, Expr, Local}; use crate::maud::{ast::*, escape}; pub fn generate(markups: Markups, output_ident: Ident) -> TokenStream { let mut build = Builder::new(output_ident.clone()); Generator::new(output_ident).markups(markups, &mut build); build.finish() } struct Generator { output_ident: Ident, } impl Generator { fn new(output_ident: Ident) -> Generator { Generator { output_ident } } fn builder(&self) -> Builder { Builder::new(self.output_ident.clone()) } fn markups>(&self, markups: Markups, build: &mut Builder) { for markup in markups.markups { self.markup(markup, build); } } fn markup>(&self, markup: Markup, build: &mut Builder) { match markup { Markup::Block(block) => { if block.markups.markups.iter().any(|markup| { matches!( *markup, Markup::ControlFlow(ControlFlow { kind: ControlFlowKind::Let(_), .. }) ) }) { self.block(block, build); } else { self.markups(block.markups, build); } } Markup::Lit(lit) => build.push_escaped(&lit.to_string()), Markup::Splice { expr, .. } => self.splice(expr, build), Markup::Element(element) => self.element(element.into(), build), Markup::ControlFlow(control_flow) => self.control_flow(control_flow, build), Markup::Semi(_) => {} } } fn block>(&self, block: Block, build: &mut Builder) { let markups = { let mut build = self.builder(); self.markups(block.markups, &mut build); build.finish() }; build.push_tokens(quote!({ #markups })); } fn splice(&self, expr: Expr, build: &mut Builder) { let output_ident = &self.output_ident; build.push_tokens( quote!(pagetop::html::html_private::render_to!(&(#expr), &mut #output_ident);), ); } fn element(&self, element: Element, build: &mut Builder) { let element_name = element.name.clone().unwrap_or_else(|| parse_quote!(div)); build.push_str("<"); self.name(element_name.clone(), build); self.attrs(element.attrs, build); build.push_str(">"); if let ElementBody::Block(block) = element.body { self.markups(block.markups, build); build.push_str(""); } } fn name(&self, name: HtmlName, build: &mut Builder) { build.push_escaped(&name.to_string()); } fn name_or_markup(&self, name: HtmlNameOrMarkup, build: &mut Builder) { match name { HtmlNameOrMarkup::HtmlName(name) => self.name(name, build), HtmlNameOrMarkup::Markup(markup) => self.markup(markup, build), } } fn attr(&self, name: HtmlName, value: AttributeType, build: &mut Builder) { match value { AttributeType::Normal { value, .. } => { build.push_str(" "); self.name(name, build); build.push_str("=\""); self.markup(value, build); build.push_str("\""); } AttributeType::Optional { toggler: Toggler { cond, .. }, .. } => { let inner_value: Expr = parse_quote!(inner_value); let body = { let mut build = self.builder(); build.push_str(" "); self.name(name, &mut build); build.push_str("=\""); self.splice(inner_value.clone(), &mut build); build.push_str("\""); build.finish() }; build.push_tokens(quote!(if let Some(#inner_value) = (#cond) { #body })); } AttributeType::Empty(None) => { build.push_str(" "); self.name(name, build); } AttributeType::Empty(Some(Toggler { cond, .. })) => { let body = { let mut build = self.builder(); build.push_str(" "); self.name(name, &mut build); build.finish() }; build.push_tokens(quote!(if (#cond) { #body })); } } } fn attrs(&self, attrs: Vec, build: &mut Builder) { let (classes, id, named_attrs) = split_attrs(attrs); if !classes.is_empty() { let mut toggle_class_exprs = vec![]; build.push_str(" "); self.name(parse_quote!(class), build); build.push_str("=\""); for (i, (name, toggler)) in classes.into_iter().enumerate() { if let Some(toggler) = toggler { toggle_class_exprs.push((i > 0, name, toggler)); } else { if i > 0 { build.push_str(" "); } self.name_or_markup(name, build); } } for (not_first, name, toggler) in toggle_class_exprs { let body = { let mut build = self.builder(); if not_first { build.push_str(" "); } self.name_or_markup(name, &mut build); build.finish() }; build.push_tokens(quote!(if (#toggler) { #body })); } build.push_str("\""); } if let Some(id) = id { build.push_str(" "); self.name(parse_quote!(id), build); build.push_str("=\""); self.name_or_markup(id, build); build.push_str("\""); } for (name, attr_type) in named_attrs { self.attr(name, attr_type, build); } } fn control_flow>(&self, control_flow: ControlFlow, build: &mut Builder) { match control_flow.kind { ControlFlowKind::If(if_) => self.control_flow_if(if_, build), ControlFlowKind::Let(let_) => self.control_flow_let(let_, build), ControlFlowKind::For(for_) => self.control_flow_for(for_, build), ControlFlowKind::While(while_) => self.control_flow_while(while_, build), ControlFlowKind::Match(match_) => self.control_flow_match(match_, build), } } fn control_flow_if>( &self, IfExpr { if_token, cond, then_branch, else_branch, }: IfExpr, build: &mut Builder, ) { build.push_tokens(quote!(#if_token #cond)); self.block(then_branch, build); if let Some((_, else_token, if_or_block)) = else_branch { build.push_tokens(quote!(#else_token)); self.control_flow_if_or_block(*if_or_block, build); } } fn control_flow_if_or_block>( &self, if_or_block: IfOrBlock, build: &mut Builder, ) { match if_or_block { IfOrBlock::If(if_) => self.control_flow_if(if_, build), IfOrBlock::Block(block) => self.block(block, build), } } fn control_flow_let(&self, let_: Local, build: &mut Builder) { build.push_tokens(let_.to_token_stream()); } fn control_flow_for>( &self, ForExpr { for_token, pat, in_token, expr, body, }: ForExpr, build: &mut Builder, ) { build.push_tokens(quote!(#for_token #pat #in_token (#expr))); self.block(body, build); } fn control_flow_while>( &self, WhileExpr { while_token, cond, body, }: WhileExpr, build: &mut Builder, ) { build.push_tokens(quote!(#while_token #cond)); self.block(body, build); } fn control_flow_match>( &self, MatchExpr { match_token, expr, brace_token, arms, }: MatchExpr, build: &mut Builder, ) { let arms = { let mut build = self.builder(); for MatchArm { pat, guard, fat_arrow_token, body, comma_token, } in arms { build.push_tokens(quote!(#pat)); if let Some((if_token, cond)) = guard { build.push_tokens(quote!(#if_token #cond)); } build.push_tokens(quote!(#fat_arrow_token)); self.block( Block { brace_token: Brace(Span::call_site()), markups: Markups { markups: vec![body], }, }, &mut build, ); build.push_tokens(quote!(#comma_token)); } build.finish() }; let mut arm_block = TokenStream::new(); brace_token.surround(&mut arm_block, |tokens| { arms.to_tokens(tokens); }); build.push_tokens(quote!(#match_token #expr #arm_block)); } } //////////////////////////////////////////////////////// #[allow(clippy::type_complexity)] fn split_attrs( attrs: Vec, ) -> ( Vec<(HtmlNameOrMarkup, Option)>, Option, Vec<(HtmlName, AttributeType)>, ) { let mut classes = vec![]; let mut id = None; let mut named_attrs = vec![]; for attr in attrs { match attr { Attribute::Class { name, toggler, .. } => { classes.push((name, toggler.map(|toggler| toggler.cond))) } Attribute::Id { name, .. } => id = Some(name), Attribute::Named { name, attr_type } => named_attrs.push((name, attr_type)), } } (classes, id, named_attrs) } //////////////////////////////////////////////////////// struct Builder { output_ident: Ident, tokens: TokenStream, tail: String, } impl Builder { fn new(output_ident: Ident) -> Builder { Builder { output_ident, tokens: TokenStream::new(), tail: String::new(), } } fn push_str(&mut self, string: &'static str) { self.tail.push_str(string); } fn push_escaped(&mut self, string: &str) { escape::escape_to_string(string, &mut self.tail); } fn push_tokens(&mut self, tokens: TokenStream) { self.cut(); self.tokens.extend(tokens); } fn cut(&mut self) { if self.tail.is_empty() { return; } let push_str_expr = { let output_ident = self.output_ident.clone(); let tail = &self.tail; quote!(#output_ident.push_str(#tail);) }; self.tail.clear(); self.tokens.extend(push_str_expr); } fn finish(mut self) -> TokenStream { self.cut(); self.tokens } }