1use std::{
2 borrow::Cow,
3 fmt::{self, Write},
4 marker::PhantomData,
5 ops,
6};
7
8use askama_escape::Escaper;
9use parser::Element;
10use proc_macro2::{Delimiter, Span, TokenStream, TokenTree};
11use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
12use syn::{parse_quote, punctuated::Punctuated, Ident, Token};
13use templr_parser::{self as parser, Node, TemplBody};
14
15const SELF_CLOSING: &[&str] = &[
16 "arena", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
17 "track", "wbr", ];
19
20#[rustfmt::skip]
21fn can_attrs_break(attrs: &[syn::Attribute]) -> bool {
22 !attrs.iter().all(|attr| {
23 attr.path().get_ident().is_some_and(|ident| {
24 [
25 "cfg", "cfg_attr",
27 "test", "ignore", "should_panic",
29 "derive", "automatically_derived",
31 "macro_export", "macro_use", "proc_macro", "proc_macro_derive",
33 "proc_macro_attribute",
34 "allow", "warn", "deny", "forbid", "deprecated", "must_use",
36 "link", "link_name", "link_ordinal", "no_link", "repr", "crate_type",
38 "no_main", "export_name", "link_section", "no_mangle", "used", "crate_name",
39 "inline", "cold", "no_builtins", "target_feature", "track_caller",
41 "instruction_set",
42 "doc",
44 "no_std", "no_implicit_prelude",
46 "path",
48 "recursion_limit", "type_length_limit",
50 "panic_handler", "global_allocator", "windows_subsystem",
52 "feature",
54 "non_exhaustive",
56 "debugger_visualizer",
58 ]
59 .iter()
60 .any(|s| ident == s)
61 })
62 })
63}
64
65fn can_macro_break(mac: &syn::Macro) -> bool {
66 !mac.path.get_ident().is_some_and(|ident| {
67 if ["cfg", "stringify", "concat", "include_str", "include_bytes"]
70 .iter()
71 .any(|s| ident == s)
72 {
73 true
74 } else if [
75 "todo",
76 "unreachable",
77 "unimplemented",
78 "panic",
79 "assert",
80 "assert_eq",
81 "assert_ne",
82 "debug_assert",
83 "debug_assert_eq",
84 "debug_assert_ne",
85 "dbg",
86 "print",
87 "println",
88 "write",
89 "writeln",
90 "format",
91 "format_args",
92 ]
93 .iter()
94 .any(|s| ident == s)
95 {
96 mac.parse_body_with(Punctuated::<syn::Expr, Token![,]>::parse_terminated)
97 .ok()
98 .map_or(false, |exprs| !exprs.iter().any(can_expr_break))
99 } else {
100 false
101 }
102 })
103}
104
105fn can_expr_break(expr: &syn::Expr) -> bool {
106 match expr {
107 syn::Expr::Array(expr) => {
108 can_attrs_break(&expr.attrs) || expr.elems.iter().any(can_expr_break)
109 }
110 syn::Expr::Assign(expr) => {
111 can_attrs_break(&expr.attrs)
112 || can_expr_break(&expr.left)
113 || can_expr_break(&expr.right)
114 }
115 syn::Expr::Async(_) => false,
116 syn::Expr::Await(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.base),
117 syn::Expr::Binary(expr) => {
118 can_attrs_break(&expr.attrs)
119 || can_expr_break(&expr.left)
120 || can_expr_break(&expr.right)
121 }
122 syn::Expr::Block(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
123 syn::Expr::Break(expr) => {
124 can_attrs_break(&expr.attrs)
125 || expr.label.is_none()
126 || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
127 }
128 syn::Expr::Call(expr) => {
129 can_attrs_break(&expr.attrs)
130 || can_expr_break(&expr.func)
131 || expr.args.iter().any(can_expr_break)
132 }
133 syn::Expr::Cast(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
134 syn::Expr::Closure(expr) => can_attrs_break(&expr.attrs),
135 syn::Expr::Const(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
136 syn::Expr::Continue(expr) => can_attrs_break(&expr.attrs) || expr.label.is_none(),
137 syn::Expr::Field(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.base),
138 syn::Expr::ForLoop(expr) => can_attrs_break(&expr.attrs),
139 syn::Expr::Group(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
140 syn::Expr::If(expr) => {
141 can_attrs_break(&expr.attrs)
142 || can_expr_break(&expr.cond)
143 || can_block_break(&expr.then_branch)
144 || expr
145 .else_branch
146 .as_ref()
147 .is_some_and(|(_, expr)| can_expr_break(expr))
148 }
149 syn::Expr::Index(expr) => {
150 can_attrs_break(&expr.attrs)
151 || can_expr_break(&expr.expr)
152 || can_expr_break(&expr.index)
153 }
154 syn::Expr::Infer(_) => false,
155 syn::Expr::Let(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
156 syn::Expr::Lit(expr) => can_attrs_break(&expr.attrs),
157 syn::Expr::Loop(expr) => can_attrs_break(&expr.attrs),
158 syn::Expr::Macro(syn::ExprMacro { attrs, mac }) => {
159 can_attrs_break(attrs) || can_macro_break(mac)
160 }
161 syn::Expr::Match(expr) => {
162 can_attrs_break(&expr.attrs)
163 || can_expr_break(&expr.expr)
164 || expr.arms.iter().any(|arm| {
165 !arm.attrs.is_empty()
166 || arm
167 .guard
168 .as_ref()
169 .is_some_and(|(_, expr)| can_expr_break(expr))
170 || can_expr_break(&expr.expr)
171 })
172 }
173 syn::Expr::MethodCall(expr) => {
174 can_attrs_break(&expr.attrs)
175 || can_expr_break(&expr.receiver)
176 || expr.args.iter().any(can_expr_break)
177 }
178 syn::Expr::Paren(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
179 syn::Expr::Path(expr) => can_attrs_break(&expr.attrs),
180 syn::Expr::Range(expr) => {
181 can_attrs_break(&expr.attrs)
182 || expr.start.as_ref().is_some_and(|expr| can_expr_break(expr))
183 || expr.end.as_ref().is_some_and(|expr| can_expr_break(expr))
184 }
185 syn::Expr::Reference(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
186 syn::Expr::Repeat(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
187 syn::Expr::Return(expr) => {
188 can_attrs_break(&expr.attrs)
189 || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
190 }
191 syn::Expr::Struct(expr) => {
192 can_attrs_break(&expr.attrs)
193 || expr.fields.iter().any(|expr| can_expr_break(&expr.expr))
194 }
195 syn::Expr::Try(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
196 syn::Expr::TryBlock(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
197 syn::Expr::Tuple(expr) => {
198 can_attrs_break(&expr.attrs) || expr.elems.iter().any(can_expr_break)
199 }
200 syn::Expr::Unary(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.expr),
201 syn::Expr::Unsafe(expr) => can_attrs_break(&expr.attrs) || can_block_break(&expr.block),
202 syn::Expr::Verbatim(_) => true,
203 syn::Expr::While(expr) => can_attrs_break(&expr.attrs) || can_expr_break(&expr.cond),
204 syn::Expr::Yield(expr) => {
205 can_attrs_break(&expr.attrs)
206 || expr.expr.as_ref().is_some_and(|expr| can_expr_break(expr))
207 }
208 _ => true,
209 }
210}
211
212fn can_block_break(block: &syn::Block) -> bool {
213 block.stmts.iter().any(|stmt| match stmt {
214 syn::Stmt::Item(_) => false,
215 syn::Stmt::Local(syn::Local { attrs, init, .. }) => {
216 can_attrs_break(attrs)
217 || init
218 .as_ref()
219 .is_some_and(|syn::LocalInit { expr, diverge, .. }| {
220 can_expr_break(expr)
221 || diverge
222 .as_ref()
223 .is_some_and(|(_, expr)| can_expr_break(expr))
224 })
225 }
226 syn::Stmt::Macro(syn::StmtMacro { attrs, mac, .. }) => {
227 can_attrs_break(attrs) || can_macro_break(mac)
228 }
229 syn::Stmt::Expr(expr, _) => can_expr_break(expr),
230 })
231}
232
233fn try_stringify_expr(expr: &syn::Expr, can_break: bool) -> Option<(Span, String)> {
234 if can_break && can_expr_break(expr) {
235 return None;
236 }
237 match expr {
238 syn::Expr::Lit(syn::ExprLit { attrs, lit }) if attrs.is_empty() => match lit {
239 syn::Lit::Str(s) => Some((s.span(), s.value())),
240 syn::Lit::Byte(byte) => Some((byte.span(), byte.value().to_string())),
241 syn::Lit::Char(ch) => Some((ch.span(), ch.value().to_string())),
242 syn::Lit::Int(int) => Some((int.span(), int.to_string())),
243 syn::Lit::Float(float) => Some((float.span(), float.to_string())),
244 syn::Lit::Bool(bool) => Some((bool.span(), bool.value().to_string())),
245 _ => None,
246 },
247 syn::Expr::Paren(syn::ExprParen { expr, attrs, .. }) if attrs.is_empty() => {
248 try_stringify_expr(expr, false)
249 }
250 syn::Expr::Block(syn::ExprBlock {
251 block,
252 attrs,
253 label: None,
254 ..
255 }) if attrs.is_empty() => try_stringify_block(block, false),
256 _ => None,
257 }
258}
259
260fn try_stringify_block(block: &syn::Block, can_break: bool) -> Option<(Span, String)> {
261 if can_break && can_block_break(block) {
262 return None;
263 }
264 let Some(syn::Stmt::Expr(expr, None)) = block.stmts.iter().rfind(|stmt| {
265 matches!(
266 stmt,
267 syn::Stmt::Expr(_, _) | syn::Stmt::Macro(_) | syn::Stmt::Local(_),
268 )
269 }) else {
270 return None;
271 };
272 try_stringify_expr(expr, false)
273}
274
275fn writer() -> Ident {
276 Ident::new("__templr_writer", Span::mixed_site())
277}
278
279fn context() -> Ident {
280 Ident::new("__templr_ctx", Span::mixed_site())
281}
282
283fn crate_path(span: Span) -> syn::Path {
284 syn::parse_quote_spanned!(span => ::templr)
285}
286
287fn isolate_block(tokens: impl ToTokens) -> TokenStream {
289 let mut iter = tokens.into_token_stream().into_iter();
290 let Some(first) = iter.next() else {
291 if cfg!(debug_assertions) {
292 eprintln!("something weird happened")
293 }
294 return quote! { () };
295 };
296
297 let first_span = first.span();
298 let group = match (first, iter.next()) {
299 (TokenTree::Group(first), None) if first.delimiter() == Delimiter::Brace => {
300 first.into_token_stream()
301 }
302 (first, second) => quote_spanned! { first_span => { #first #second #(#iter)* } },
303 };
304
305 quote_spanned! { first_span =>
306 loop {
307 #[allow(unreachable_code)]
308 break {
309 #[warn(unreachable_code)]
310 #group
311 };
312 }
313 }
314}
315
316const EST_EXPR_SIZE: usize = 20;
317
318fn call_on_block(
320 tokens: &mut TokenStream,
321 block: &syn::Block,
322 f: impl FnOnce(&mut TokenStream, TokenStream),
323) {
324 match block.stmts.iter().rposition(|stmt| {
325 matches!(
326 stmt,
327 syn::Stmt::Expr(_, _) | syn::Stmt::Macro(_) | syn::Stmt::Local(_),
328 )
329 }) {
330 Some(i)
331 if matches!(
332 block.stmts[i],
333 syn::Stmt::Expr(_, None)
334 | syn::Stmt::Macro(syn::StmtMacro {
335 semi_token: None,
336 ..
337 })
338 ) && !can_block_break(block) =>
339 {
340 let (before, after) = block.stmts.split_at(i);
341 let (stmt, after) = after.split_first().unwrap();
342
343 let mut inner_tokens = TokenStream::new();
344 inner_tokens.append_all(before);
345 f(
346 &mut inner_tokens,
347 match stmt {
348 syn::Stmt::Expr(expr, None) => expr.to_token_stream(),
349 syn::Stmt::Macro(
350 mac @ syn::StmtMacro {
351 semi_token: None, ..
352 },
353 ) => mac.to_token_stream(),
354 _ => unreachable!(),
355 },
356 );
357 inner_tokens.append_all(after);
358 surround_with_block(tokens, block.brace_token.span.join(), inner_tokens, false);
359 }
360 _ => f(tokens, isolate_block(block)),
361 }
362}
363
364fn call_on_maybe_block(
365 tokens: &mut TokenStream,
366 block: &parser::Block,
367 f: impl FnOnce(&mut TokenStream, TokenStream),
368) {
369 match block {
370 parser::Block::Valid(block) => call_on_block(tokens, block, f),
371 parser::Block::Invalid { body, .. } => f(tokens, isolate_block(body)),
372 }
373}
374
375fn surround_with_block(
376 tokens: &mut TokenStream,
377 span: Span,
378 inner_tokens: TokenStream,
379 require_brace: bool,
380) {
381 let mut inner_tokens = inner_tokens.into_iter().peekable();
382 match inner_tokens.next() {
383 Some(TokenTree::Group(first))
384 if first.delimiter() == Delimiter::Brace && inner_tokens.peek().is_none() =>
385 {
386 let inner = first.stream();
387 tokens.append_all(quote_spanned!(span => { #inner }));
388 }
389 Some(first) => tokens.append_all(quote_spanned!(span => {
390 #first
391 #(#inner_tokens)*
392 })),
393 None if require_brace => tokens.append_all(quote_spanned!(span => {})),
394 None => {}
395 }
396}
397
398enum BufEntry {
399 Token(TokenTree),
400 LitStr(syn::LitStr),
401 Str(Cow<'static, str>),
402}
403
404impl BufEntry {
405 fn lit_str(span: Span, value: &str) -> Self {
406 Self::LitStr(syn::LitStr::new(value, span))
407 }
408
409 fn str(value: impl Into<Cow<'static, str>>) -> Self {
410 Self::Str(value.into())
411 }
412
413 fn stringify(&self, span: Span) -> TokenStream {
414 match self {
415 BufEntry::Str(s) => quote_spanned! { span => #s },
416 BufEntry::Token(token) => {
417 quote_spanned! { span => stringify!(#token) }
418 }
419 BufEntry::LitStr(lit) => lit.to_token_stream(),
420 }
421 }
422}
423
424struct Buf(Vec<BufEntry>);
425
426impl Buf {
427 fn push_str(&mut self, s: impl Into<Cow<'static, str>>) {
428 self.push(BufEntry::str(s));
429 }
430 fn push_lit_str(&mut self, span: Span, value: &str) {
431 self.push(BufEntry::lit_str(span, value));
432 }
433 fn extend_tokens(&mut self, tokens: impl ToTokens) {
434 for token in tokens.into_token_stream() {
435 self.push(BufEntry::Token(token));
436 }
437 }
438
439 fn name(&mut self, name: &parser::Name) {
440 match name {
441 parser::Name::Str(s) => self.push(BufEntry::LitStr(s.clone())),
442 parser::Name::Parts(parts) => {
443 for part in parts {
444 self.extend_tokens(part);
445 }
446 }
447 }
448 }
449}
450
451impl ops::Deref for Buf {
452 type Target = Vec<BufEntry>;
453
454 fn deref(&self) -> &Self::Target {
455 &self.0
456 }
457}
458
459impl ops::DerefMut for Buf {
460 fn deref_mut(&mut self) -> &mut Self::Target {
461 &mut self.0
462 }
463}
464
465struct Generator<'a> {
466 buf: Buf,
467 sizes: Vec<usize>,
468 space: bool,
469 _phantom: PhantomData<&'a TemplBody>,
470}
471
472impl<'a> Generator<'a> {
473 fn new() -> Self {
474 Self {
475 buf: Buf(vec![]),
476 sizes: vec![],
477 space: false,
478 _phantom: PhantomData,
479 }
480 }
481
482 fn write_escaped(&mut self, span: Span, value: impl fmt::Display) {
483 pub struct EscapeWriter<'a, T>(&'a mut T);
484
485 impl<T: Write> Write for EscapeWriter<'_, T> {
486 #[inline]
487 fn write_str(&mut self, s: &str) -> fmt::Result {
488 askama_escape::Html.write_escaped(&mut *self.0, s)
489 }
490 }
491
492 let mut s = String::new();
493 write!(EscapeWriter(&mut s), "{value}").unwrap();
494 self.buf.push_lit_str(span, &s);
495 }
496
497 fn top_size(&mut self) -> &mut usize {
498 self.sizes.last_mut().unwrap()
499 }
500
501 #[inline]
502 fn flush_buffer(&mut self, tokens: &mut TokenStream, span: Span) {
503 *self.top_size() += self.buf.len();
504 let buf = &mut self.buf;
505 if !buf.is_empty() {
506 let writer = writer();
507
508 let entries = buf.drain(..).map(|e| e.stringify(span));
509
510 tokens.append_all(quote_spanned! { span =>
511 ::core::fmt::Write::write_str(#writer, concat!(#(#entries),*))?;
512 });
513 }
514 }
515
516 fn write_maybe_block(&mut self, tokens: &mut TokenStream, block: &parser::Block) {
517 match block {
518 parser::Block::Valid(block) => self.write_block(tokens, block),
519 parser::Block::Invalid { brace, body } => {
520 let block_span = brace.span.join();
521
522 let crate_path = crate_path(block_span);
523 let writer = writer();
524
525 self.flush_buffer(tokens, brace.span.open());
526 *self.top_size() += EST_EXPR_SIZE;
527
528 let body = isolate_block(body);
529 tokens.append_all(quote_spanned! { block_span =>
530 #crate_path::write_escaped(#writer, &#body)?;
531 });
532 }
533 }
534 }
535
536 fn write_block(&mut self, tokens: &mut TokenStream, block: &syn::Block) {
537 match try_stringify_block(block, true) {
538 Some((span, s)) => {
539 tokens.append_all(quote_spanned! { block.brace_token.span.open() => _ = });
540 match can_block_break(block) {
541 true => tokens.append_all(isolate_block(block)),
542 false => {
543 block.brace_token.surround(tokens, |tokens| {
544 tokens.append_all(quote_spanned! { block.brace_token.span.open() =>
545 _ = 0;
546 });
547 tokens.append_all(&block.stmts);
548 });
549 }
550 }
551 tokens.append_all(quote_spanned! { block.brace_token.span.close() => ; });
552 self.write_escaped(span, &s);
553 }
554 None => {
555 self.flush_buffer(tokens, block.brace_token.span.open());
556 call_on_block(tokens, block, |tokens, expr| {
557 let block_span = block.brace_token.span.join();
558 let crate_path = crate_path(block_span);
559 let writer = writer();
560
561 *self.top_size() += EST_EXPR_SIZE;
562 tokens.append_all(quote_spanned! { block_span =>
563 #crate_path::write_escaped(#writer, &(#expr))?;
564 });
565 });
566 }
567 }
568 }
569
570 fn write_templ(&mut self, tokens: &mut TokenStream, span: Span, body: &'a TemplBody) {
571 let crate_path = crate_path(Span::mixed_site());
572 let context = context();
573 let writer = writer();
574
575 self.sizes.push(0);
576 let mut inner_tokens = TokenStream::new();
577 for node in &body.nodes {
578 self.write_node(&mut inner_tokens, node);
579 }
580 self.flush_buffer(&mut inner_tokens, span);
581 let size = self.sizes.pop().unwrap();
582
583 let context_ty = body
584 .use_context()
585 .and_then(|u| u.colon_ty.as_ref())
586 .map(|(colon, ty)| quote! { #colon #ty });
587 let context_pat_owned;
588 let context_pat: &dyn ToTokens = match body.use_context() {
589 Some(u) => match &u.as_pat {
590 Some((_, pat)) => pat,
591 None => &u.context,
592 },
593 None => {
594 context_pat_owned = quote_spanned! { span => _ };
595 &context_pat_owned
596 }
597 };
598 let children_owned;
599 let children: &dyn ToTokens = match body.use_children() {
600 Some(u) => match &u.as_pat {
601 Some((_, pat)) => pat,
602 None => &u.children,
603 },
604 None => {
605 children_owned = quote_spanned! { span => _ };
606 &children_owned
607 }
608 };
609 tokens.append_all(quote_spanned! { span =>
610 #crate_path::FnTemplate::new_sized(
611 #size,
612 move |#writer, #context @ #context_pat #context_ty, #children| {
613 #inner_tokens
614 #crate_path::Result::Ok(())
615 },
616 )
617 })
618 }
619
620 fn write_match<T>(
621 &mut self,
622 tokens: &mut TokenStream,
623 stmt: &'a parser::Match<T>,
624 mut write_item: impl FnMut(&mut Self, &mut TokenStream, &'a T),
625 ) {
626 let parser::Match {
627 pound,
628 match_token,
629 expr,
630 brace,
631 arms,
632 } = stmt;
633
634 self.flush_buffer(tokens, pound.span);
635
636 let num_arms = arms.len();
637 self.sizes.push(0);
638 let init_space = self.space;
639
640 let arms = arms.iter().map(
641 |parser::match_stmt::Arm {
642 pat,
643 guard,
644 fat_arrow,
645 brace,
646 body,
647 }| {
648 let mut inner_tokens = TokenStream::new();
649 self.space = init_space;
650 for item in body {
651 write_item(self, &mut inner_tokens, item);
652 }
653 self.flush_buffer(&mut inner_tokens, brace.span.close());
654 self.space = true;
655
656 let guard = guard
657 .as_ref()
658 .map(|(if_token, cond)| quote! { #if_token #cond });
659
660 let mut tokens = quote! { #pat #guard #fat_arrow };
661 surround_with_block(&mut tokens, brace.span.join(), inner_tokens, true);
662 tokens
663 },
664 );
665
666 tokens.append_all(quote_spanned! { brace.span.join() =>
667 #match_token #expr { #(#arms)* }
668 });
669
670 let avg_size = self.sizes.pop().unwrap() / num_arms;
671 *self.top_size() += avg_size;
672 }
673
674 fn write_if<T>(
675 &mut self,
676 tokens: &mut TokenStream,
677 stmt: &'a parser::If<T>,
678 mut write_item: impl FnMut(&mut Self, &mut TokenStream, &'a T),
679 ) {
680 let parser::If {
681 pound,
682 if_token,
683 cond,
684 brace,
685 body,
686 else_if_branches,
687 else_branch,
688 } = stmt;
689
690 self.flush_buffer(tokens, pound.span);
691
692 let num_branches = 1 + else_if_branches.len() + else_branch.is_some() as usize;
693 self.sizes.push(0);
694
695 let init_space = self.space;
696 let mut write_nodes = |tokens: &mut _, span, body| {
697 self.space = init_space;
698 for item in body {
699 write_item(self, tokens, item);
700 }
701 self.flush_buffer(tokens, span);
702 self.space = true;
703 };
704
705 let mut then_tokens = TokenStream::new();
706 write_nodes(&mut then_tokens, brace.span.open(), body);
707
708 tokens.append_all(quote! { #if_token #cond });
709 surround_with_block(tokens, brace.span.join(), then_tokens, true);
710 for branch in else_if_branches {
711 let parser::if_stmt::ElseIfBranch {
712 else_token,
713 if_token,
714 cond,
715 brace,
716 body,
717 } = branch;
718 let mut then_tokens = TokenStream::new();
719 write_nodes(&mut then_tokens, brace.span.open(), body);
720
721 tokens.append_all(quote! { #else_token #if_token #cond });
722 surround_with_block(tokens, brace.span.join(), then_tokens, true);
723 }
724 if let Some(parser::if_stmt::ElseBranch {
725 else_token,
726 brace,
727 body,
728 }) = else_branch
729 {
730 let mut then_tokens = TokenStream::new();
731 write_nodes(&mut then_tokens, brace.span.open(), body);
732
733 tokens.append_all(quote! { #else_token });
734 surround_with_block(tokens, brace.span.join(), then_tokens, true);
735 }
736
737 let avg_size = self.sizes.pop().unwrap() / num_branches;
738 *self.top_size() += avg_size;
739 }
740
741 fn write_for<T>(
742 &mut self,
743 tokens: &mut TokenStream,
744 stmt: &'a parser::For<T>,
745 mut write_item: impl FnMut(&mut Self, &mut TokenStream, &'a T),
746 ) {
747 let parser::For {
748 pound,
749 for_token,
750 pat,
751 in_token,
752 expr,
753 brace,
754 body,
755 } = stmt;
756 self.flush_buffer(tokens, pound.span);
757
758 let mut inner_tokens = TokenStream::new();
759 self.space = true;
760 for item in body {
761 write_item(self, &mut inner_tokens, item);
762 }
763 self.flush_buffer(&mut inner_tokens, brace.span.close());
764
765 tokens.append_all(quote! { #for_token #pat #in_token #expr });
766 surround_with_block(tokens, brace.span.join(), inner_tokens, true);
767 }
768
769 fn write_attr(&mut self, tokens: &mut TokenStream, attr: &'a parser::Attr) {
770 match attr {
771 parser::Attr::Html(parser::attrs::HtmlAttr { name, value }) => match value {
772 parser::attrs::HtmlAttrValue::None => {
773 self.buf.push_str(" ");
774 self.buf.name(name);
775 }
776 parser::attrs::HtmlAttrValue::Ident(eq, value) => {
777 self.buf.push_str(" ");
778 self.buf.name(name);
779 self.buf.extend_tokens(quote! { #eq #value });
780 }
781 parser::attrs::HtmlAttrValue::Str(eq, value) => {
782 self.buf.push_str(" ");
783 self.buf.name(name);
784 self.buf.extend_tokens(eq);
785 self.buf.push_str("\"");
786 self.write_escaped(value.span(), value.value());
787 self.buf.push_str("\"");
788 }
789 parser::attrs::HtmlAttrValue::Int(eq, value) => {
790 self.buf.push_str(" ");
791 self.buf.name(name);
792 self.buf.extend_tokens(eq);
793 self.buf.push_str("\"");
794 self.write_escaped(value.span(), value);
795 self.buf.push_str("\"");
796 }
797 parser::attrs::HtmlAttrValue::Float(eq, value) => {
798 self.buf.push_str(" ");
799 self.buf.name(name);
800 self.buf.extend_tokens(eq);
801 self.buf.push_str("\"");
802 self.write_escaped(value.span(), value);
803 self.buf.push_str("\"");
804 }
805 parser::attrs::HtmlAttrValue::Block(toggle, eq, cond) => match toggle {
806 Some(toggle) => {
807 self.flush_buffer(tokens, toggle.span);
808
809 call_on_maybe_block(tokens, cond, |tokens, expr| {
810 let crate_path = crate_path(toggle.span);
811 let writer = writer();
812
813 let mut inner_tokens = TokenStream::new();
814
815 self.buf.push_str(" ");
816 self.buf.name(name);
817 self.flush_buffer(&mut inner_tokens, cond.brace_span().open());
818
819 *self.top_size() += EST_EXPR_SIZE;
820 tokens.append_all(quote_spanned! { toggle.span =>
821 _ = 0;
822 if let Some(view) = #crate_path::OptAttrValue::to_opt_attr_value(#expr) {
823 #inner_tokens
824 #crate_path::write_escaped(#writer, &view)?;
825 }
826 });
827 });
828 }
829 None => {
830 self.buf.push_str(" ");
831 self.buf.name(name);
832 self.buf.extend_tokens(eq);
833 self.buf.push_str("\"");
834 self.write_maybe_block(tokens, cond);
835 self.buf.push_str("\"");
836 }
837 },
838 },
839 parser::Attr::If(stmt) => self.write_if(tokens, stmt, Self::write_attr),
840 parser::Attr::Match(stmt) => self.write_match(tokens, stmt, Self::write_attr),
841 parser::Attr::Scope(parser::Scope { brace, body, .. }) => {
842 brace.surround(tokens, |tokens| {
843 for attr in body {
844 self.write_attr(tokens, attr);
845 }
846 })
847 }
848 parser::Attr::Let(stmt) => {
849 stmt.let_token.to_tokens(tokens);
850 stmt.pat.to_tokens(tokens);
851 stmt.init.to_tokens(tokens);
852 stmt.semi.to_tokens(tokens);
853 }
854 parser::Attr::Spread(block) => call_on_maybe_block(tokens, block, |tokens, expr| {
855 self.flush_buffer(tokens, block.brace_span().open());
856
857 let block_span = block.brace_span().join();
858 let crate_path = crate_path(block_span);
859 let writer = writer();
860
861 *self.top_size() += EST_EXPR_SIZE;
862 tokens.append_all(quote_spanned! { block_span =>
863 #crate_path::Attributes::render_into(#expr, #writer)?;
864 });
865 }),
866 }
867 }
868
869 fn write_element(&mut self, tokens: &mut TokenStream, element: &'a Element) {
870 if self.space {
871 self.buf.push_str(" ");
872 }
873 self.space = false;
874
875 let parser::element::OpenTag {
876 lt,
877 name,
878 attrs,
879 slash,
880 gt,
881 } = &element.open;
882
883 self.buf.extend_tokens(lt);
884 self.buf.name(name);
885
886 let mut inner_tokens = TokenStream::new();
887 for attr in attrs {
888 self.write_attr(&mut inner_tokens, attr);
889 }
890 surround_with_block(tokens, lt.span, inner_tokens, false);
891 self.buf.extend_tokens(gt);
892
893 let mut inner_tokens = TokenStream::new();
894 for node in &element.nodes {
895 self.write_node(&mut inner_tokens, node);
896 }
897 surround_with_block(tokens, gt.span, inner_tokens, false);
898
899 if let Some(slash) = slash {
900 if !SELF_CLOSING.contains(&&*name.to_string()) {
901 self.buf.extend_tokens(quote! { #lt #slash });
902 self.buf.name(name);
903 self.buf.extend_tokens(gt);
904 }
905 } else if let Some(parser::element::CloseTag {
906 lt,
907 slash,
908 name,
909 gt,
910 }) = &element.close
911 {
912 self.buf.extend_tokens(quote! { #lt #slash });
913 self.buf.name(name);
914 self.buf.extend_tokens(gt);
915 }
916 self.space = true;
917 }
918
919 fn write_call(&mut self, tokens: &mut TokenStream, call: &'a parser::Call) {
920 let parser::Call { pound, expr, end } = call;
921
922 self.flush_buffer(tokens, pound.span);
923
924 let expr = match can_expr_break(expr) {
925 true => isolate_block(expr),
926 false => expr.to_token_stream(),
927 };
928
929 let crate_path = crate_path(pound.span);
930 let context = context();
931 let writer = writer();
932
933 match end {
934 parser::call::End::Semi(semi) => {
935 tokens.append_all(quote_spanned! { pound.span =>
936 #crate_path::Template::render_into(
937 &(#expr),
938 #writer,
939 #context
940 )? #semi
941 });
942 }
943 parser::call::End::Children(brace, body) => {
944 let mut inner_tokens = TokenStream::new();
945
946 self.write_templ(&mut inner_tokens, brace.span.join(), body);
947
948 tokens.append_all(quote_spanned! { pound.span =>
949 #crate_path::Template::render_with_children_into(
950 &(#expr),
951 #writer,
952 #context,
953 &#inner_tokens,
954 )?;
955 });
956 }
957 }
958 }
959
960 fn write_node(&mut self, tokens: &mut TokenStream, node: &'a Node) {
961 match node {
962 Node::Entity(entity) => self.buf.extend_tokens(entity),
963 Node::Doctype(parser::Doctype {
964 lt,
965 bang,
966 doctype,
967 name,
968 gt,
969 }) => {
970 self.buf.extend_tokens(quote! { #lt #bang #doctype });
971 self.buf.push_str(" ");
972 self.buf.name(name);
973 self.buf.extend_tokens(gt);
974 }
975 Node::Element(element) => self.write_element(tokens, element),
976 Node::RawText(text) => {
977 for token in text.tokens.clone() {
978 match token {
979 TokenTree::Punct(punct)
980 if matches!(
981 punct.as_char(),
982 '.' | ',' | ':' | ';' | '!' | '?' | '%'
983 ) =>
984 {
985 self.space = true;
986 self.buf.extend_tokens(punct)
987 }
988 TokenTree::Punct(punct)
989 if matches!(punct.as_char(), '$' | '#' | '¿' | '¡') =>
990 {
991 if self.space {
992 self.space = false;
993 self.buf.push_str(" ");
994 }
995 self.buf.extend_tokens(punct)
996 }
997 _ => {
998 if self.space {
999 self.buf.push_str(" ");
1000 }
1001 self.space = true;
1002 self.write_escaped(token.span(), token);
1003 }
1004 }
1005 }
1006 }
1007 Node::Paren(paren, nodes) => {
1008 if self.space {
1009 self.buf.push_str(" ");
1010 }
1011 self.buf.push_lit_str(paren.span.open(), "(");
1012 self.space = false;
1013 for node in nodes {
1014 self.write_node(tokens, node);
1015 }
1016 self.buf.push_lit_str(paren.span.close(), ")");
1017 }
1018 Node::Bracket(bracket, nodes) => {
1019 if self.space {
1020 self.buf.push_str(" ");
1021 }
1022 self.buf.push_lit_str(bracket.span.open(), "[");
1023 self.space = false;
1024 for node in nodes {
1025 self.write_node(tokens, node);
1026 }
1027 self.buf.push_lit_str(bracket.span.close(), "]");
1028 }
1029 Node::Expr(block) => {
1030 if self.space {
1031 self.buf.push_str(" ");
1032 }
1033 self.space = true;
1034 self.write_maybe_block(tokens, block);
1035 }
1036 Node::If(stmt) => self.write_if(tokens, stmt, Self::write_node),
1037 Node::Match(stmt) => self.write_match(tokens, stmt, Self::write_node),
1038 Node::For(stmt) => self.write_for(tokens, stmt, Self::write_node),
1039 Node::With(parser::With {
1040 with,
1041 ty,
1042 eq,
1043 expr,
1044 brace,
1045 nodes,
1046 ..
1047 }) => {
1048 brace.surround(tokens, |tokens| {
1049 let context = context();
1050 let ty = ty.as_ref().map(|(col, ty)| quote! { #col #ty });
1051 tokens.append_all(quote_spanned! { with.span =>
1052 let #context #ty #eq #expr;
1053 });
1054 for node in nodes {
1055 self.write_node(tokens, node);
1056 }
1057 });
1058 }
1059 Node::Scope(parser::Scope { brace, body, .. }) => brace.surround(tokens, |tokens| {
1060 for node in body {
1061 self.write_node(tokens, node);
1062 }
1063 }),
1064 Node::Let(stmt) => {
1065 stmt.let_token.to_tokens(tokens);
1066 stmt.pat.to_tokens(tokens);
1067 stmt.init.to_tokens(tokens);
1068 stmt.semi.to_tokens(tokens);
1069 }
1070 Node::Call(call) => self.write_call(tokens, call),
1071 }
1072 }
1073}
1074
1075#[proc_macro]
1262pub fn templ(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
1263 let body = syn::parse_macro_input!(tokens as TemplBody);
1264
1265 let mut tokens = TokenStream::new();
1266 let mut generator = Generator::new();
1267 generator.write_templ(&mut tokens, Span::call_site(), &body);
1268 tokens.into()
1269}
1270
1271struct AttrsBody(Vec<parser::Attr>);
1272
1273impl syn::parse::Parse for AttrsBody {
1274 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1275 let mut attrs = vec![];
1276 while !input.is_empty() {
1277 attrs.push(input.parse()?);
1278 }
1279 Ok(Self(attrs))
1280 }
1281}
1282
1283#[proc_macro]
1305pub fn attrs(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
1306 let body = syn::parse_macro_input!(tokens as AttrsBody);
1307
1308 let mut inner_tokens = TokenStream::new();
1309 let mut generator = Generator::new();
1310 generator.sizes.push(0);
1311 for attr in &body.0 {
1312 generator.write_attr(&mut inner_tokens, attr);
1313 }
1314 generator.flush_buffer(&mut inner_tokens, Span::call_site());
1315
1316 let writer = writer();
1317 let crate_path = crate_path(Span::call_site());
1318 From::from(quote! {
1319 (move || -> #crate_path::Result<_> {
1320 let mut #writer = ::std::string::String::new();
1321 {
1322 let #writer = &mut #writer;
1323 #inner_tokens
1324 }
1325 #crate_path::Result::Ok(
1326 #crate_path::attrs::PrerenderedAttrs::from_raw_unchecked(#writer),
1327 )
1328 })()
1329 })
1330}
1331
1332#[proc_macro_derive(Template)]
1358pub fn derive_template(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
1359 let input = syn::parse_macro_input!(tokens as syn::DeriveInput);
1360
1361 let name = input.ident;
1362
1363 let crate_path = crate_path(Span::call_site());
1364
1365 let ctx_ty = Ident::new("__TemplrCtx", Span::mixed_site());
1366 let (_, ty_generics, _) = input.generics.split_for_impl();
1367
1368 let mut modified_generics = input.generics.clone();
1369 modified_generics.params.push(parse_quote! {
1370 #ctx_ty: ?Sized
1371 });
1372 modified_generics
1373 .make_where_clause()
1374 .predicates
1375 .push(parse_quote! {
1376 Self: #crate_path::ToTemplate<#ctx_ty>
1377 });
1378
1379 let (impl_generics, _, where_clause) = modified_generics.split_for_impl();
1380
1381 From::from(quote! {
1382 impl #impl_generics #crate_path::Template<#ctx_ty> for #name #ty_generics
1383 #where_clause
1384 {
1385 fn size_hint(&self) -> usize {
1386 #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1387 .size_hint()
1388 }
1389 fn render_with_children_into(
1390 &self,
1391 writer: &mut dyn ::std::fmt::Write,
1392 ctx: &#ctx_ty,
1393 children: &dyn #crate_path::Template<#ctx_ty>,
1394 ) -> #crate_path::Result<()> {
1395 #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1396 .render_with_children_into(writer, ctx, children)
1397 }
1398 fn render_into(&self, writer: &mut dyn ::std::fmt::Write, ctx: &#ctx_ty) -> #crate_path::Result<()> {
1399 #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1400 .render_into(writer, ctx)
1401 }
1402 fn write_into(&self, writer: &mut dyn ::std::io::Write, ctx: &#ctx_ty) -> ::std::io::Result<()> {
1403 #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1404 .write_into(writer, ctx)
1405 }
1406 fn render(&self, ctx: &#ctx_ty) -> #crate_path::Result<String> {
1407 #crate_path::ToTemplate::<#ctx_ty>::to_template(self)
1408 .render(ctx)
1409 }
1410 }
1411 })
1412}