1 | use std::collections::hash_map::{Entry, HashMap}; |
2 | use std::path::Path; |
3 | use std::{cmp, hash, mem, str}; |
4 | |
5 | use crate::config::WhitespaceHandling; |
6 | use crate::heritage::{Context, Heritage}; |
7 | use crate::input::{Source, TemplateInput}; |
8 | use crate::CompileError; |
9 | |
10 | use parser::node::{ |
11 | Call, Comment, CondTest, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws, |
12 | }; |
13 | use parser::{Expr, Node}; |
14 | use quote::quote; |
15 | |
16 | pub(crate) struct Generator<'a> { |
17 | // The template input state: original struct AST and attributes |
18 | input: &'a TemplateInput<'a>, |
19 | // All contexts, keyed by the package-relative template path |
20 | contexts: &'a HashMap<&'a Path, Context<'a>>, |
21 | // The heritage contains references to blocks and their ancestry |
22 | heritage: Option<&'a Heritage<'a>>, |
23 | // Variables accessible directly from the current scope (not redirected to context) |
24 | locals: MapChain<'a, &'a str, LocalMeta>, |
25 | // Suffix whitespace from the previous literal. Will be flushed to the |
26 | // output buffer unless suppressed by whitespace suppression on the next |
27 | // non-literal. |
28 | next_ws: Option<&'a str>, |
29 | // Whitespace suppression from the previous non-literal. Will be used to |
30 | // determine whether to flush prefix whitespace from the next literal. |
31 | skip_ws: WhitespaceHandling, |
32 | // If currently in a block, this will contain the name of a potential parent block |
33 | super_block: Option<(&'a str, usize)>, |
34 | // buffer for writable |
35 | buf_writable: Vec<Writable<'a>>, |
36 | // Counter for write! hash named arguments |
37 | named: usize, |
38 | } |
39 | |
40 | impl<'a> Generator<'a> { |
41 | pub(crate) fn new<'n>( |
42 | input: &'n TemplateInput<'_>, |
43 | contexts: &'n HashMap<&'n Path, Context<'n>>, |
44 | heritage: Option<&'n Heritage<'_>>, |
45 | locals: MapChain<'n, &'n str, LocalMeta>, |
46 | ) -> Generator<'n> { |
47 | Generator { |
48 | input, |
49 | contexts, |
50 | heritage, |
51 | locals, |
52 | next_ws: None, |
53 | skip_ws: WhitespaceHandling::Preserve, |
54 | super_block: None, |
55 | buf_writable: vec![], |
56 | named: 0, |
57 | } |
58 | } |
59 | |
60 | // Takes a Context and generates the relevant implementations. |
61 | pub(crate) fn build(mut self, ctx: &'a Context<'_>) -> Result<String, CompileError> { |
62 | let mut buf = Buffer::new(0); |
63 | |
64 | self.impl_template(ctx, &mut buf)?; |
65 | self.impl_display(&mut buf)?; |
66 | |
67 | #[cfg (feature = "with-actix-web" )] |
68 | self.impl_actix_web_responder(&mut buf)?; |
69 | #[cfg (feature = "with-axum" )] |
70 | self.impl_axum_into_response(&mut buf)?; |
71 | #[cfg (feature = "with-gotham" )] |
72 | self.impl_gotham_into_response(&mut buf)?; |
73 | #[cfg (feature = "with-hyper" )] |
74 | self.impl_hyper_into_response(&mut buf)?; |
75 | #[cfg (feature = "with-mendes" )] |
76 | self.impl_mendes_responder(&mut buf)?; |
77 | #[cfg (feature = "with-rocket" )] |
78 | self.impl_rocket_responder(&mut buf)?; |
79 | #[cfg (feature = "with-tide" )] |
80 | self.impl_tide_integrations(&mut buf)?; |
81 | #[cfg (feature = "with-warp" )] |
82 | self.impl_warp_reply(&mut buf)?; |
83 | |
84 | Ok(buf.buf) |
85 | } |
86 | |
87 | // Implement `Template` for the given context struct. |
88 | fn impl_template( |
89 | &mut self, |
90 | ctx: &'a Context<'_>, |
91 | buf: &mut Buffer, |
92 | ) -> Result<(), CompileError> { |
93 | self.write_header(buf, "::askama::Template" , None)?; |
94 | buf.writeln( |
95 | "fn render_into(&self, writer: &mut (impl ::std::fmt::Write + ?Sized)) -> \ |
96 | ::askama::Result<()> {" , |
97 | )?; |
98 | |
99 | // Make sure the compiler understands that the generated code depends on the template files. |
100 | for path in self.contexts.keys() { |
101 | // Skip the fake path of templates defined in rust source. |
102 | let path_is_valid = match self.input.source { |
103 | Source::Path(_) => true, |
104 | Source::Source(_) => path != &self.input.path, |
105 | }; |
106 | if path_is_valid { |
107 | let path = path.to_str().unwrap(); |
108 | buf.writeln( |
109 | "e! { |
110 | include_bytes!(#path); |
111 | } |
112 | .to_string(), |
113 | )?; |
114 | } |
115 | } |
116 | |
117 | let size_hint = if let Some(heritage) = self.heritage { |
118 | self.handle(heritage.root, heritage.root.nodes, buf, AstLevel::Top) |
119 | } else { |
120 | self.handle(ctx, ctx.nodes, buf, AstLevel::Top) |
121 | }?; |
122 | |
123 | self.flush_ws(Ws(None, None)); |
124 | buf.writeln("::askama::Result::Ok(())" )?; |
125 | buf.writeln("}" )?; |
126 | |
127 | buf.writeln("const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = " )?; |
128 | buf.writeln(&format!(" {:?}" , self.input.extension()))?; |
129 | buf.writeln(";" )?; |
130 | |
131 | buf.writeln("const SIZE_HINT: ::std::primitive::usize = " )?; |
132 | buf.writeln(&format!(" {size_hint}" ))?; |
133 | buf.writeln(";" )?; |
134 | |
135 | buf.writeln("const MIME_TYPE: &'static ::std::primitive::str = " )?; |
136 | buf.writeln(&format!(" {:?}" , &self.input.mime_type))?; |
137 | buf.writeln(";" )?; |
138 | |
139 | buf.writeln("}" )?; |
140 | Ok(()) |
141 | } |
142 | |
143 | // Implement `Display` for the given context struct. |
144 | fn impl_display(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
145 | self.write_header(buf, "::std::fmt::Display" , None)?; |
146 | buf.writeln("#[inline]" )?; |
147 | buf.writeln("fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {" )?; |
148 | buf.writeln("::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})" )?; |
149 | buf.writeln("}" )?; |
150 | buf.writeln("}" ) |
151 | } |
152 | |
153 | // Implement Actix-web's `Responder`. |
154 | #[cfg (feature = "with-actix-web" )] |
155 | fn impl_actix_web_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
156 | self.write_header(buf, "::askama_actix::actix_web::Responder" , None)?; |
157 | buf.writeln("type Body = ::askama_actix::actix_web::body::BoxBody;" )?; |
158 | buf.writeln("#[inline]" )?; |
159 | buf.writeln( |
160 | "fn respond_to(self, _req: &::askama_actix::actix_web::HttpRequest) \ |
161 | -> ::askama_actix::actix_web::HttpResponse<Self::Body> {" , |
162 | )?; |
163 | buf.writeln("<Self as ::askama_actix::TemplateToResponse>::to_response(&self)" )?; |
164 | buf.writeln("}" )?; |
165 | buf.writeln("}" ) |
166 | } |
167 | |
168 | // Implement Axum's `IntoResponse`. |
169 | #[cfg (feature = "with-axum" )] |
170 | fn impl_axum_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
171 | self.write_header(buf, "::askama_axum::IntoResponse" , None)?; |
172 | buf.writeln("#[inline]" )?; |
173 | buf.writeln( |
174 | "fn into_response(self)\ |
175 | -> ::askama_axum::Response {" , |
176 | )?; |
177 | buf.writeln("::askama_axum::into_response(&self)" )?; |
178 | buf.writeln("}" )?; |
179 | buf.writeln("}" ) |
180 | } |
181 | |
182 | // Implement gotham's `IntoResponse`. |
183 | #[cfg (feature = "with-gotham" )] |
184 | fn impl_gotham_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
185 | self.write_header(buf, "::askama_gotham::IntoResponse" , None)?; |
186 | buf.writeln("#[inline]" )?; |
187 | buf.writeln( |
188 | "fn into_response(self, _state: &::askama_gotham::State)\ |
189 | -> ::askama_gotham::Response<::askama_gotham::Body> {" , |
190 | )?; |
191 | buf.writeln("::askama_gotham::respond(&self)" )?; |
192 | buf.writeln("}" )?; |
193 | buf.writeln("}" ) |
194 | } |
195 | |
196 | // Implement `From<Template> for hyper::Response<Body>` and `From<Template> for hyper::Body. |
197 | #[cfg (feature = "with-hyper" )] |
198 | fn impl_hyper_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
199 | let (impl_generics, orig_ty_generics, where_clause) = |
200 | self.input.ast.generics.split_for_impl(); |
201 | let ident = &self.input.ast.ident; |
202 | // From<Template> for hyper::Response<Body> |
203 | buf.writeln(&format!( |
204 | " {} {{" , |
205 | quote!( |
206 | impl #impl_generics ::core::convert::From<&#ident #orig_ty_generics> |
207 | for ::askama_hyper::hyper::Response<::askama_hyper::hyper::Body> |
208 | #where_clause |
209 | ) |
210 | ))?; |
211 | buf.writeln("#[inline]" )?; |
212 | buf.writeln(&format!( |
213 | " {} {{" , |
214 | quote!(fn from(value: &#ident #orig_ty_generics) -> Self) |
215 | ))?; |
216 | buf.writeln("::askama_hyper::respond(value)" )?; |
217 | buf.writeln("}" )?; |
218 | buf.writeln("}" )?; |
219 | |
220 | // TryFrom<Template> for hyper::Body |
221 | buf.writeln(&format!( |
222 | " {} {{" , |
223 | quote!( |
224 | impl #impl_generics ::core::convert::TryFrom<&#ident #orig_ty_generics> |
225 | for ::askama_hyper::hyper::Body |
226 | #where_clause |
227 | ) |
228 | ))?; |
229 | buf.writeln("type Error = ::askama::Error;" )?; |
230 | buf.writeln("#[inline]" )?; |
231 | buf.writeln(&format!( |
232 | " {} {{" , |
233 | quote!(fn try_from(value: &#ident #orig_ty_generics) -> Result<Self, Self::Error>) |
234 | ))?; |
235 | buf.writeln("::askama::Template::render(value).map(Into::into)" )?; |
236 | buf.writeln("}" )?; |
237 | buf.writeln("}" ) |
238 | } |
239 | |
240 | // Implement mendes' `Responder`. |
241 | #[cfg (feature = "with-mendes" )] |
242 | fn impl_mendes_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
243 | let param = syn::parse_str("A: ::mendes::Application" ).unwrap(); |
244 | |
245 | let mut generics = self.input.ast.generics.clone(); |
246 | generics.params.push(param); |
247 | let (_, orig_ty_generics, _) = self.input.ast.generics.split_for_impl(); |
248 | let (impl_generics, _, where_clause) = generics.split_for_impl(); |
249 | |
250 | let mut where_clause = match where_clause { |
251 | Some(clause) => clause.clone(), |
252 | None => syn::WhereClause { |
253 | where_token: syn::Token![where](proc_macro2::Span::call_site()), |
254 | predicates: syn::punctuated::Punctuated::new(), |
255 | }, |
256 | }; |
257 | |
258 | where_clause |
259 | .predicates |
260 | .push(syn::parse_str("A::ResponseBody: From<String>" ).unwrap()); |
261 | where_clause |
262 | .predicates |
263 | .push(syn::parse_str("A::Error: From<::askama_mendes::Error>" ).unwrap()); |
264 | |
265 | buf.writeln( |
266 | format!( |
267 | " {} {} for {} {} {{" , |
268 | quote!(impl #impl_generics), |
269 | "::mendes::application::IntoResponse<A>" , |
270 | self.input.ast.ident, |
271 | quote!(#orig_ty_generics #where_clause), |
272 | ) |
273 | .as_ref(), |
274 | )?; |
275 | |
276 | buf.writeln( |
277 | "fn into_response(self, app: &A, req: &::mendes::http::request::Parts) \ |
278 | -> ::mendes::http::Response<A::ResponseBody> {" , |
279 | )?; |
280 | |
281 | buf.writeln("::askama_mendes::into_response(app, req, &self)" )?; |
282 | buf.writeln("}" )?; |
283 | buf.writeln("}" )?; |
284 | Ok(()) |
285 | } |
286 | |
287 | // Implement Rocket's `Responder`. |
288 | #[cfg (feature = "with-rocket" )] |
289 | fn impl_rocket_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
290 | let lifetime1 = syn::Lifetime::new("'askama1" , proc_macro2::Span::call_site()); |
291 | let lifetime2 = syn::Lifetime::new("'askama2" , proc_macro2::Span::call_site()); |
292 | |
293 | let mut param2 = syn::LifetimeParam::new(lifetime2); |
294 | param2.colon_token = Some(syn::Token![:](proc_macro2::Span::call_site())); |
295 | param2.bounds = syn::punctuated::Punctuated::new(); |
296 | param2.bounds.push_value(lifetime1.clone()); |
297 | |
298 | let param1 = syn::GenericParam::Lifetime(syn::LifetimeParam::new(lifetime1)); |
299 | let param2 = syn::GenericParam::Lifetime(param2); |
300 | |
301 | self.write_header( |
302 | buf, |
303 | "::askama_rocket::Responder<'askama1, 'askama2>" , |
304 | Some(vec![param1, param2]), |
305 | )?; |
306 | |
307 | buf.writeln("#[inline]" )?; |
308 | buf.writeln( |
309 | "fn respond_to(self, _: &'askama1 ::askama_rocket::Request) \ |
310 | -> ::askama_rocket::Result<'askama2> {" , |
311 | )?; |
312 | buf.writeln("::askama_rocket::respond(&self)" )?; |
313 | |
314 | buf.writeln("}" )?; |
315 | buf.writeln("}" )?; |
316 | Ok(()) |
317 | } |
318 | |
319 | #[cfg (feature = "with-tide" )] |
320 | fn impl_tide_integrations(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
321 | self.write_header( |
322 | buf, |
323 | "::std::convert::TryInto<::askama_tide::tide::Body>" , |
324 | None, |
325 | )?; |
326 | buf.writeln( |
327 | "type Error = ::askama_tide::askama::Error; \n\ |
328 | #[inline] \n\ |
329 | fn try_into(self) -> ::askama_tide::askama::Result<::askama_tide::tide::Body> {" , |
330 | )?; |
331 | buf.writeln("::askama_tide::try_into_body(&self)" )?; |
332 | buf.writeln("}" )?; |
333 | buf.writeln("}" )?; |
334 | |
335 | buf.writeln("#[allow(clippy::from_over_into)]" )?; |
336 | self.write_header(buf, "Into<::askama_tide::tide::Response>" , None)?; |
337 | buf.writeln("#[inline]" )?; |
338 | buf.writeln("fn into(self) -> ::askama_tide::tide::Response {" )?; |
339 | buf.writeln("::askama_tide::into_response(&self)" )?; |
340 | buf.writeln("} \n}" ) |
341 | } |
342 | |
343 | #[cfg (feature = "with-warp" )] |
344 | fn impl_warp_reply(&mut self, buf: &mut Buffer) -> Result<(), CompileError> { |
345 | self.write_header(buf, "::askama_warp::warp::reply::Reply" , None)?; |
346 | buf.writeln("#[inline]" )?; |
347 | buf.writeln("fn into_response(self) -> ::askama_warp::warp::reply::Response {" )?; |
348 | buf.writeln("::askama_warp::reply(&self)" )?; |
349 | buf.writeln("}" )?; |
350 | buf.writeln("}" ) |
351 | } |
352 | |
353 | // Writes header for the `impl` for `TraitFromPathName` or `Template` |
354 | // for the given context struct. |
355 | fn write_header( |
356 | &mut self, |
357 | buf: &mut Buffer, |
358 | target: &str, |
359 | params: Option<Vec<syn::GenericParam>>, |
360 | ) -> Result<(), CompileError> { |
361 | let mut generics = self.input.ast.generics.clone(); |
362 | if let Some(params) = params { |
363 | for param in params { |
364 | generics.params.push(param); |
365 | } |
366 | } |
367 | let (_, orig_ty_generics, _) = self.input.ast.generics.split_for_impl(); |
368 | let (impl_generics, _, where_clause) = generics.split_for_impl(); |
369 | buf.writeln( |
370 | format!( |
371 | " {} {} for {}{} {{" , |
372 | quote!(impl #impl_generics), |
373 | target, |
374 | self.input.ast.ident, |
375 | quote!(#orig_ty_generics #where_clause), |
376 | ) |
377 | .as_ref(), |
378 | ) |
379 | } |
380 | |
381 | /* Helper methods for handling node types */ |
382 | |
383 | fn handle( |
384 | &mut self, |
385 | ctx: &'a Context<'_>, |
386 | nodes: &'a [Node<'_>], |
387 | buf: &mut Buffer, |
388 | level: AstLevel, |
389 | ) -> Result<usize, CompileError> { |
390 | let mut size_hint = 0; |
391 | for n in nodes { |
392 | match *n { |
393 | Node::Lit(ref lit) => { |
394 | self.visit_lit(lit); |
395 | } |
396 | Node::Comment(ref comment) => { |
397 | self.write_comment(comment); |
398 | } |
399 | Node::Expr(ws, ref val) => { |
400 | self.write_expr(ws, val); |
401 | } |
402 | Node::Let(ref l) => { |
403 | self.write_let(buf, l)?; |
404 | } |
405 | Node::If(ref i) => { |
406 | size_hint += self.write_if(ctx, buf, i)?; |
407 | } |
408 | Node::Match(ref m) => { |
409 | size_hint += self.write_match(ctx, buf, m)?; |
410 | } |
411 | Node::Loop(ref loop_block) => { |
412 | size_hint += self.write_loop(ctx, buf, loop_block)?; |
413 | } |
414 | Node::BlockDef(ref b) => { |
415 | size_hint += self.write_block(buf, Some(b.name), Ws(b.ws1.0, b.ws2.1))?; |
416 | } |
417 | Node::Include(ref i) => { |
418 | size_hint += self.handle_include(ctx, buf, i)?; |
419 | } |
420 | Node::Call(ref call) => { |
421 | size_hint += self.write_call(ctx, buf, call)?; |
422 | } |
423 | Node::Macro(ref m) => { |
424 | if level != AstLevel::Top { |
425 | return Err("macro blocks only allowed at the top level" .into()); |
426 | } |
427 | self.flush_ws(m.ws1); |
428 | self.prepare_ws(m.ws2); |
429 | } |
430 | Node::Raw(ref raw) => { |
431 | self.handle_ws(raw.ws1); |
432 | self.visit_lit(&raw.lit); |
433 | self.handle_ws(raw.ws2); |
434 | } |
435 | Node::Import(ref i) => { |
436 | if level != AstLevel::Top { |
437 | return Err("import blocks only allowed at the top level" .into()); |
438 | } |
439 | self.handle_ws(i.ws); |
440 | } |
441 | Node::Extends(_) => { |
442 | if level != AstLevel::Top { |
443 | return Err("extend blocks only allowed at the top level" .into()); |
444 | } |
445 | // No whitespace handling: child template top-level is not used, |
446 | // except for the blocks defined in it. |
447 | } |
448 | Node::Break(ws) => { |
449 | self.handle_ws(ws); |
450 | self.write_buf_writable(buf)?; |
451 | buf.writeln("break;" )?; |
452 | } |
453 | Node::Continue(ws) => { |
454 | self.handle_ws(ws); |
455 | self.write_buf_writable(buf)?; |
456 | buf.writeln("continue;" )?; |
457 | } |
458 | } |
459 | } |
460 | |
461 | if AstLevel::Top == level { |
462 | // Handle any pending whitespace. |
463 | if self.next_ws.is_some() { |
464 | self.flush_ws(Ws(Some(self.skip_ws.into()), None)); |
465 | } |
466 | |
467 | size_hint += self.write_buf_writable(buf)?; |
468 | } |
469 | Ok(size_hint) |
470 | } |
471 | |
472 | fn write_if( |
473 | &mut self, |
474 | ctx: &'a Context<'_>, |
475 | buf: &mut Buffer, |
476 | i: &'a If<'_>, |
477 | ) -> Result<usize, CompileError> { |
478 | let mut flushed = 0; |
479 | let mut arm_sizes = Vec::new(); |
480 | let mut has_else = false; |
481 | for (i, cond) in i.branches.iter().enumerate() { |
482 | self.handle_ws(cond.ws); |
483 | flushed += self.write_buf_writable(buf)?; |
484 | if i > 0 { |
485 | self.locals.pop(); |
486 | } |
487 | |
488 | self.locals.push(); |
489 | let mut arm_size = 0; |
490 | if let Some(CondTest { target, expr }) = &cond.cond { |
491 | if i == 0 { |
492 | buf.write("if " ); |
493 | } else { |
494 | buf.dedent()?; |
495 | buf.write("} else if " ); |
496 | } |
497 | |
498 | if let Some(target) = target { |
499 | let mut expr_buf = Buffer::new(0); |
500 | self.visit_expr(&mut expr_buf, expr)?; |
501 | buf.write("let " ); |
502 | self.visit_target(buf, true, true, target); |
503 | buf.write(" = &(" ); |
504 | buf.write(&expr_buf.buf); |
505 | buf.write(")" ); |
506 | } else { |
507 | // The following syntax `*(&(...) as &bool)` is used to |
508 | // trigger Rust's automatic dereferencing, to coerce |
509 | // e.g. `&&&&&bool` to `bool`. First `&(...) as &bool` |
510 | // coerces e.g. `&&&bool` to `&bool`. Then `*(&bool)` |
511 | // finally dereferences it to `bool`. |
512 | buf.write("*(&(" ); |
513 | let expr_code = self.visit_expr_root(expr)?; |
514 | buf.write(&expr_code); |
515 | buf.write(") as &bool)" ); |
516 | } |
517 | } else { |
518 | buf.dedent()?; |
519 | buf.write("} else" ); |
520 | has_else = true; |
521 | } |
522 | |
523 | buf.writeln(" {" )?; |
524 | |
525 | arm_size += self.handle(ctx, &cond.nodes, buf, AstLevel::Nested)?; |
526 | arm_sizes.push(arm_size); |
527 | } |
528 | self.handle_ws(i.ws); |
529 | flushed += self.write_buf_writable(buf)?; |
530 | buf.writeln("}" )?; |
531 | |
532 | self.locals.pop(); |
533 | |
534 | if !has_else { |
535 | arm_sizes.push(0); |
536 | } |
537 | Ok(flushed + median(&mut arm_sizes)) |
538 | } |
539 | |
540 | #[allow (clippy::too_many_arguments)] |
541 | fn write_match( |
542 | &mut self, |
543 | ctx: &'a Context<'_>, |
544 | buf: &mut Buffer, |
545 | m: &'a Match<'a>, |
546 | ) -> Result<usize, CompileError> { |
547 | let Match { |
548 | ws1, |
549 | ref expr, |
550 | ref arms, |
551 | ws2, |
552 | } = *m; |
553 | |
554 | self.flush_ws(ws1); |
555 | let flushed = self.write_buf_writable(buf)?; |
556 | let mut arm_sizes = Vec::new(); |
557 | |
558 | let expr_code = self.visit_expr_root(expr)?; |
559 | buf.writeln(&format!("match & {expr_code} {{" ))?; |
560 | |
561 | let mut arm_size = 0; |
562 | for (i, arm) in arms.iter().enumerate() { |
563 | self.handle_ws(arm.ws); |
564 | |
565 | if i > 0 { |
566 | arm_sizes.push(arm_size + self.write_buf_writable(buf)?); |
567 | |
568 | buf.writeln("}" )?; |
569 | self.locals.pop(); |
570 | } |
571 | |
572 | self.locals.push(); |
573 | self.visit_target(buf, true, true, &arm.target); |
574 | buf.writeln(" => {" )?; |
575 | |
576 | arm_size = self.handle(ctx, &arm.nodes, buf, AstLevel::Nested)?; |
577 | } |
578 | |
579 | self.handle_ws(ws2); |
580 | arm_sizes.push(arm_size + self.write_buf_writable(buf)?); |
581 | buf.writeln("}" )?; |
582 | self.locals.pop(); |
583 | |
584 | buf.writeln("}" )?; |
585 | |
586 | Ok(flushed + median(&mut arm_sizes)) |
587 | } |
588 | |
589 | #[allow (clippy::too_many_arguments)] |
590 | fn write_loop( |
591 | &mut self, |
592 | ctx: &'a Context<'_>, |
593 | buf: &mut Buffer, |
594 | loop_block: &'a Loop<'_>, |
595 | ) -> Result<usize, CompileError> { |
596 | self.handle_ws(loop_block.ws1); |
597 | self.locals.push(); |
598 | |
599 | let expr_code = self.visit_expr_root(&loop_block.iter)?; |
600 | |
601 | let has_else_nodes = !loop_block.else_nodes.is_empty(); |
602 | |
603 | let flushed = self.write_buf_writable(buf)?; |
604 | buf.writeln("{" )?; |
605 | if has_else_nodes { |
606 | buf.writeln("let mut _did_loop = false;" )?; |
607 | } |
608 | match loop_block.iter { |
609 | Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {expr_code};" )), |
610 | Expr::Array(..) => buf.writeln(&format!("let _iter = {expr_code}.iter();" )), |
611 | // If `iter` is a call then we assume it's something that returns |
612 | // an iterator. If not then the user can explicitly add the needed |
613 | // call without issues. |
614 | Expr::Call(..) | Expr::Index(..) => { |
615 | buf.writeln(&format!("let _iter = ( {expr_code}).into_iter();" )) |
616 | } |
617 | // If accessing `self` then it most likely needs to be |
618 | // borrowed, to prevent an attempt of moving. |
619 | _ if expr_code.starts_with("self." ) => { |
620 | buf.writeln(&format!("let _iter = (& {expr_code}).into_iter();" )) |
621 | } |
622 | // If accessing a field then it most likely needs to be |
623 | // borrowed, to prevent an attempt of moving. |
624 | Expr::Attr(..) => buf.writeln(&format!("let _iter = (& {expr_code}).into_iter();" )), |
625 | // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`. |
626 | _ => buf.writeln(&format!("let _iter = ( {expr_code}).into_iter();" )), |
627 | }?; |
628 | if let Some(cond) = &loop_block.cond { |
629 | self.locals.push(); |
630 | buf.write("let _iter = _iter.filter(|" ); |
631 | self.visit_target(buf, true, true, &loop_block.var); |
632 | buf.write("| -> bool {" ); |
633 | self.visit_expr(buf, cond)?; |
634 | buf.writeln("});" )?; |
635 | self.locals.pop(); |
636 | } |
637 | |
638 | self.locals.push(); |
639 | buf.write("for (" ); |
640 | self.visit_target(buf, true, true, &loop_block.var); |
641 | buf.writeln(", _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {" )?; |
642 | |
643 | if has_else_nodes { |
644 | buf.writeln("_did_loop = true;" )?; |
645 | } |
646 | let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?; |
647 | self.handle_ws(loop_block.ws2); |
648 | size_hint1 += self.write_buf_writable(buf)?; |
649 | self.locals.pop(); |
650 | buf.writeln("}" )?; |
651 | |
652 | let mut size_hint2; |
653 | if has_else_nodes { |
654 | buf.writeln("if !_did_loop {" )?; |
655 | self.locals.push(); |
656 | size_hint2 = self.handle(ctx, &loop_block.else_nodes, buf, AstLevel::Nested)?; |
657 | self.handle_ws(loop_block.ws3); |
658 | size_hint2 += self.write_buf_writable(buf)?; |
659 | self.locals.pop(); |
660 | buf.writeln("}" )?; |
661 | } else { |
662 | self.handle_ws(loop_block.ws3); |
663 | size_hint2 = self.write_buf_writable(buf)?; |
664 | } |
665 | |
666 | buf.writeln("}" )?; |
667 | |
668 | Ok(flushed + ((size_hint1 * 3) + size_hint2) / 2) |
669 | } |
670 | |
671 | fn write_call( |
672 | &mut self, |
673 | ctx: &'a Context<'_>, |
674 | buf: &mut Buffer, |
675 | call: &'a Call<'_>, |
676 | ) -> Result<usize, CompileError> { |
677 | let Call { |
678 | ws, |
679 | scope, |
680 | name, |
681 | ref args, |
682 | } = *call; |
683 | if name == "super" { |
684 | return self.write_block(buf, None, ws); |
685 | } |
686 | |
687 | let (def, own_ctx) = match scope { |
688 | Some(s) => { |
689 | let path = ctx.imports.get(s).ok_or_else(|| { |
690 | CompileError::from(format!("no import found for scope {s:?}" )) |
691 | })?; |
692 | let mctx = self |
693 | .contexts |
694 | .get(path.as_path()) |
695 | .ok_or_else(|| CompileError::from(format!("context for {path:?} not found" )))?; |
696 | let def = mctx.macros.get(name).ok_or_else(|| { |
697 | CompileError::from(format!("macro {name:?} not found in scope {s:?}" )) |
698 | })?; |
699 | (def, mctx) |
700 | } |
701 | None => { |
702 | let def = ctx |
703 | .macros |
704 | .get(name) |
705 | .ok_or_else(|| CompileError::from(format!("macro {name:?} not found" )))?; |
706 | (def, ctx) |
707 | } |
708 | }; |
709 | |
710 | self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first |
711 | self.locals.push(); |
712 | self.write_buf_writable(buf)?; |
713 | buf.writeln("{" )?; |
714 | self.prepare_ws(def.ws1); |
715 | |
716 | let mut names = Buffer::new(0); |
717 | let mut values = Buffer::new(0); |
718 | let mut is_first_variable = true; |
719 | if args.len() != def.args.len() { |
720 | return Err(CompileError::from(format!( |
721 | "macro {name:?} expected {} argument {}, found {}" , |
722 | def.args.len(), |
723 | if def.args.len() != 1 { "s" } else { "" }, |
724 | args.len() |
725 | ))); |
726 | } |
727 | let mut named_arguments = HashMap::new(); |
728 | // Since named arguments can only be passed last, we only need to check if the last argument |
729 | // is a named one. |
730 | if let Some(Expr::NamedArgument(_, _)) = args.last() { |
731 | // First we check that all named arguments actually exist in the called item. |
732 | for arg in args.iter().rev() { |
733 | let Expr::NamedArgument(arg_name, _) = arg else { |
734 | break; |
735 | }; |
736 | if !def.args.iter().any(|arg| arg == arg_name) { |
737 | return Err(CompileError::from(format!( |
738 | "no argument named ` {arg_name}` in macro {name:?}" |
739 | ))); |
740 | } |
741 | named_arguments.insert(arg_name, arg); |
742 | } |
743 | } |
744 | |
745 | // Handling both named and unnamed arguments requires to be careful of the named arguments |
746 | // order. To do so, we iterate through the macro defined arguments and then check if we have |
747 | // a named argument with this name: |
748 | // |
749 | // * If there is one, we add it and move to the next argument. |
750 | // * If there isn't one, then we pick the next argument (we can do it without checking |
751 | // anything since named arguments are always last). |
752 | let mut allow_positional = true; |
753 | for (index, arg) in def.args.iter().enumerate() { |
754 | let expr = match named_arguments.get(&arg) { |
755 | Some(expr) => { |
756 | allow_positional = false; |
757 | expr |
758 | } |
759 | None => { |
760 | if !allow_positional { |
761 | // If there is already at least one named argument, then it's not allowed |
762 | // to use unnamed ones at this point anymore. |
763 | return Err(CompileError::from(format!( |
764 | "cannot have unnamed argument (` {arg}`) after named argument in macro \ |
765 | {name:?}" |
766 | ))); |
767 | } |
768 | &args[index] |
769 | } |
770 | }; |
771 | match expr { |
772 | // If `expr` is already a form of variable then |
773 | // don't reintroduce a new variable. This is |
774 | // to avoid moving non-copyable values. |
775 | &Expr::Var(name) if name != "self" => { |
776 | let var = self.locals.resolve_or_self(name); |
777 | self.locals.insert(arg, LocalMeta::with_ref(var)); |
778 | } |
779 | Expr::Attr(obj, attr) => { |
780 | let mut attr_buf = Buffer::new(0); |
781 | self.visit_attr(&mut attr_buf, obj, attr)?; |
782 | |
783 | let var = self.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf); |
784 | self.locals.insert(arg, LocalMeta::with_ref(var)); |
785 | } |
786 | // Everything else still needs to become variables, |
787 | // to avoid having the same logic be executed |
788 | // multiple times, e.g. in the case of macro |
789 | // parameters being used multiple times. |
790 | _ => { |
791 | if is_first_variable { |
792 | is_first_variable = false |
793 | } else { |
794 | names.write(", " ); |
795 | values.write(", " ); |
796 | } |
797 | names.write(arg); |
798 | |
799 | values.write("(" ); |
800 | values.write(&self.visit_expr_root(expr)?); |
801 | values.write(")" ); |
802 | self.locals.insert_with_default(arg); |
803 | } |
804 | } |
805 | } |
806 | |
807 | debug_assert_eq!(names.buf.is_empty(), values.buf.is_empty()); |
808 | if !names.buf.is_empty() { |
809 | buf.writeln(&format!("let ( {}) = ( {});" , names.buf, values.buf))?; |
810 | } |
811 | |
812 | let mut size_hint = self.handle(own_ctx, &def.nodes, buf, AstLevel::Nested)?; |
813 | |
814 | self.flush_ws(def.ws2); |
815 | size_hint += self.write_buf_writable(buf)?; |
816 | buf.writeln("}" )?; |
817 | self.locals.pop(); |
818 | self.prepare_ws(ws); |
819 | Ok(size_hint) |
820 | } |
821 | |
822 | fn handle_include( |
823 | &mut self, |
824 | ctx: &'a Context<'_>, |
825 | buf: &mut Buffer, |
826 | i: &'a Include<'_>, |
827 | ) -> Result<usize, CompileError> { |
828 | self.flush_ws(i.ws); |
829 | self.write_buf_writable(buf)?; |
830 | let path = self |
831 | .input |
832 | .config |
833 | .find_template(i.path, Some(&self.input.path))?; |
834 | |
835 | // Make sure the compiler understands that the generated code depends on the template file. |
836 | { |
837 | let path = path.to_str().unwrap(); |
838 | buf.writeln( |
839 | "e! { |
840 | include_bytes!(#path); |
841 | } |
842 | .to_string(), |
843 | )?; |
844 | } |
845 | |
846 | // We clone the context of the child in order to preserve their macros and imports. |
847 | // But also add all the imports and macros from this template that don't override the |
848 | // child's ones to preserve this template's context. |
849 | let child_ctx = &mut self.contexts[path.as_path()].clone(); |
850 | for (name, mac) in &ctx.macros { |
851 | child_ctx.macros.entry(name).or_insert(mac); |
852 | } |
853 | for (name, import) in &ctx.imports { |
854 | child_ctx |
855 | .imports |
856 | .entry(name) |
857 | .or_insert_with(|| import.clone()); |
858 | } |
859 | |
860 | // Create a new generator for the child, and call it like in `impl_template` as if it were |
861 | // a full template, while preserving the context. |
862 | let heritage = if !child_ctx.blocks.is_empty() || child_ctx.extends.is_some() { |
863 | Some(Heritage::new(child_ctx, self.contexts)) |
864 | } else { |
865 | None |
866 | }; |
867 | |
868 | let handle_ctx = match &heritage { |
869 | Some(heritage) => heritage.root, |
870 | None => child_ctx, |
871 | }; |
872 | let locals = MapChain::with_parent(&self.locals); |
873 | let mut child = Self::new(self.input, self.contexts, heritage.as_ref(), locals); |
874 | let mut size_hint = child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?; |
875 | size_hint += child.write_buf_writable(buf)?; |
876 | self.prepare_ws(i.ws); |
877 | |
878 | Ok(size_hint) |
879 | } |
880 | |
881 | fn is_shadowing_variable(&self, var: &Target<'a>) -> Result<bool, CompileError> { |
882 | match var { |
883 | Target::Name(name) => { |
884 | let name = normalize_identifier(name); |
885 | match self.locals.get(&name) { |
886 | // declares a new variable |
887 | None => Ok(false), |
888 | // an initialized variable gets shadowed |
889 | Some(meta) if meta.initialized => Ok(true), |
890 | // initializes a variable that was introduced in a LetDecl before |
891 | _ => Ok(false), |
892 | } |
893 | } |
894 | Target::Tuple(_, targets) => { |
895 | for target in targets { |
896 | match self.is_shadowing_variable(target) { |
897 | Ok(false) => continue, |
898 | outcome => return outcome, |
899 | } |
900 | } |
901 | Ok(false) |
902 | } |
903 | Target::Struct(_, named_targets) => { |
904 | for (_, target) in named_targets { |
905 | match self.is_shadowing_variable(target) { |
906 | Ok(false) => continue, |
907 | outcome => return outcome, |
908 | } |
909 | } |
910 | Ok(false) |
911 | } |
912 | _ => Err("literals are not allowed on the left-hand side of an assignment" .into()), |
913 | } |
914 | } |
915 | |
916 | fn write_let(&mut self, buf: &mut Buffer, l: &'a Let<'_>) -> Result<(), CompileError> { |
917 | self.handle_ws(l.ws); |
918 | |
919 | let Some(val) = &l.val else { |
920 | self.write_buf_writable(buf)?; |
921 | buf.write("let " ); |
922 | self.visit_target(buf, false, true, &l.var); |
923 | return buf.writeln(";" ); |
924 | }; |
925 | |
926 | let mut expr_buf = Buffer::new(0); |
927 | self.visit_expr(&mut expr_buf, val)?; |
928 | |
929 | let shadowed = self.is_shadowing_variable(&l.var)?; |
930 | if shadowed { |
931 | // Need to flush the buffer if the variable is being shadowed, |
932 | // to ensure the old variable is used. |
933 | self.write_buf_writable(buf)?; |
934 | } |
935 | if shadowed |
936 | || !matches!(l.var, Target::Name(_)) |
937 | || matches!(&l.var, Target::Name(name) if self.locals.get(name).is_none()) |
938 | { |
939 | buf.write("let " ); |
940 | } |
941 | |
942 | self.visit_target(buf, true, true, &l.var); |
943 | buf.writeln(&format!(" = {};" , &expr_buf.buf)) |
944 | } |
945 | |
946 | // If `name` is `Some`, this is a call to a block definition, and we have to find |
947 | // the first block for that name from the ancestry chain. If name is `None`, this |
948 | // is from a `super()` call, and we can get the name from `self.super_block`. |
949 | fn write_block( |
950 | &mut self, |
951 | buf: &mut Buffer, |
952 | name: Option<&'a str>, |
953 | outer: Ws, |
954 | ) -> Result<usize, CompileError> { |
955 | // Flush preceding whitespace according to the outer WS spec |
956 | self.flush_ws(outer); |
957 | |
958 | let prev_block = self.super_block; |
959 | let cur = match (name, prev_block) { |
960 | // The top-level context contains a block definition |
961 | (Some(cur_name), None) => (cur_name, 0), |
962 | // A block definition contains a block definition of the same name |
963 | (Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => { |
964 | return Err(format!("cannot define recursive blocks ( {cur_name})" ).into()); |
965 | } |
966 | // A block definition contains a definition of another block |
967 | (Some(cur_name), Some((_, _))) => (cur_name, 0), |
968 | // `super()` was called inside a block |
969 | (None, Some((prev_name, gen))) => (prev_name, gen + 1), |
970 | // `super()` is called from outside a block |
971 | (None, None) => return Err("cannot call 'super()' outside block" .into()), |
972 | }; |
973 | self.super_block = Some(cur); |
974 | |
975 | // Get the block definition from the heritage chain |
976 | let heritage = self |
977 | .heritage |
978 | .ok_or_else(|| CompileError::from("no block ancestors available" ))?; |
979 | let (ctx, def) = *heritage.blocks[cur.0].get(cur.1).ok_or_else(|| { |
980 | CompileError::from(match name { |
981 | None => format!("no super() block found for block ' {}'" , cur.0), |
982 | Some(name) => format!("no block found for name ' {name}'" ), |
983 | }) |
984 | })?; |
985 | |
986 | // Handle inner whitespace suppression spec and process block nodes |
987 | self.prepare_ws(def.ws1); |
988 | self.locals.push(); |
989 | let size_hint = self.handle(ctx, &def.nodes, buf, AstLevel::Block)?; |
990 | |
991 | if !self.locals.is_current_empty() { |
992 | // Need to flush the buffer before popping the variable stack |
993 | self.write_buf_writable(buf)?; |
994 | } |
995 | |
996 | self.locals.pop(); |
997 | self.flush_ws(def.ws2); |
998 | |
999 | // Restore original block context and set whitespace suppression for |
1000 | // succeeding whitespace according to the outer WS spec |
1001 | self.super_block = prev_block; |
1002 | self.prepare_ws(outer); |
1003 | Ok(size_hint) |
1004 | } |
1005 | |
1006 | fn write_expr(&mut self, ws: Ws, s: &'a Expr<'a>) { |
1007 | self.handle_ws(ws); |
1008 | self.buf_writable.push(Writable::Expr(s)); |
1009 | } |
1010 | |
1011 | // Write expression buffer and empty |
1012 | fn write_buf_writable(&mut self, buf: &mut Buffer) -> Result<usize, CompileError> { |
1013 | if self.buf_writable.is_empty() { |
1014 | return Ok(0); |
1015 | } |
1016 | |
1017 | if self |
1018 | .buf_writable |
1019 | .iter() |
1020 | .all(|w| matches!(w, Writable::Lit(_))) |
1021 | { |
1022 | let mut buf_lit = Buffer::new(0); |
1023 | for s in mem::take(&mut self.buf_writable) { |
1024 | if let Writable::Lit(s) = s { |
1025 | buf_lit.write(s); |
1026 | }; |
1027 | } |
1028 | buf.writeln(&format!("writer.write_str( {:#?})?;" , &buf_lit.buf))?; |
1029 | return Ok(buf_lit.buf.len()); |
1030 | } |
1031 | |
1032 | let mut size_hint = 0; |
1033 | let mut buf_format = Buffer::new(0); |
1034 | let mut buf_expr = Buffer::new(buf.indent + 1); |
1035 | let mut expr_cache = HashMap::with_capacity(self.buf_writable.len()); |
1036 | for s in mem::take(&mut self.buf_writable) { |
1037 | match s { |
1038 | Writable::Lit(s) => { |
1039 | buf_format.write(&s.replace('{' , "{{" ).replace('}' , "}}" )); |
1040 | size_hint += s.len(); |
1041 | } |
1042 | Writable::Expr(s) => { |
1043 | use self::DisplayWrap::*; |
1044 | let mut expr_buf = Buffer::new(0); |
1045 | let wrapped = self.visit_expr(&mut expr_buf, s)?; |
1046 | let expression = match wrapped { |
1047 | Wrapped => expr_buf.buf, |
1048 | Unwrapped => format!( |
1049 | "::askama::MarkupDisplay::new_unsafe(&( {}), {})" , |
1050 | expr_buf.buf, self.input.escaper |
1051 | ), |
1052 | }; |
1053 | |
1054 | let id = match expr_cache.entry(expression.clone()) { |
1055 | Entry::Occupied(e) if is_cacheable(s) => *e.get(), |
1056 | e => { |
1057 | let id = self.named; |
1058 | self.named += 1; |
1059 | |
1060 | buf_expr.write(&format!("expr {id} = " )); |
1061 | buf_expr.write("&" ); |
1062 | buf_expr.write(&expression); |
1063 | buf_expr.writeln("," )?; |
1064 | |
1065 | if let Entry::Vacant(e) = e { |
1066 | e.insert(id); |
1067 | } |
1068 | |
1069 | id |
1070 | } |
1071 | }; |
1072 | |
1073 | buf_format.write(&format!(" {{expr {id}}}" )); |
1074 | size_hint += 3; |
1075 | } |
1076 | } |
1077 | } |
1078 | |
1079 | buf.writeln("::std::write!(" )?; |
1080 | buf.indent(); |
1081 | buf.writeln("writer," )?; |
1082 | buf.writeln(&format!(" {:#?}," , &buf_format.buf))?; |
1083 | buf.writeln(buf_expr.buf.trim())?; |
1084 | buf.dedent()?; |
1085 | buf.writeln(")?;" )?; |
1086 | Ok(size_hint) |
1087 | } |
1088 | |
1089 | fn visit_lit(&mut self, lit: &'a Lit<'_>) { |
1090 | assert!(self.next_ws.is_none()); |
1091 | let Lit { lws, val, rws } = *lit; |
1092 | if !lws.is_empty() { |
1093 | match self.skip_ws { |
1094 | WhitespaceHandling::Suppress => {} |
1095 | _ if val.is_empty() => { |
1096 | assert!(rws.is_empty()); |
1097 | self.next_ws = Some(lws); |
1098 | } |
1099 | WhitespaceHandling::Preserve => self.buf_writable.push(Writable::Lit(lws)), |
1100 | WhitespaceHandling::Minimize => { |
1101 | self.buf_writable |
1102 | .push(Writable::Lit(match lws.contains(' \n' ) { |
1103 | true => " \n" , |
1104 | false => " " , |
1105 | })); |
1106 | } |
1107 | } |
1108 | } |
1109 | |
1110 | if !val.is_empty() { |
1111 | self.skip_ws = WhitespaceHandling::Preserve; |
1112 | self.buf_writable.push(Writable::Lit(val)); |
1113 | } |
1114 | |
1115 | if !rws.is_empty() { |
1116 | self.next_ws = Some(rws); |
1117 | } |
1118 | } |
1119 | |
1120 | fn write_comment(&mut self, comment: &'a Comment<'_>) { |
1121 | self.handle_ws(comment.ws); |
1122 | } |
1123 | |
1124 | /* Visitor methods for expression types */ |
1125 | |
1126 | fn visit_expr_root(&mut self, expr: &Expr<'_>) -> Result<String, CompileError> { |
1127 | let mut buf = Buffer::new(0); |
1128 | self.visit_expr(&mut buf, expr)?; |
1129 | Ok(buf.buf) |
1130 | } |
1131 | |
1132 | fn visit_expr( |
1133 | &mut self, |
1134 | buf: &mut Buffer, |
1135 | expr: &Expr<'_>, |
1136 | ) -> Result<DisplayWrap, CompileError> { |
1137 | Ok(match *expr { |
1138 | Expr::BoolLit(s) => self.visit_bool_lit(buf, s), |
1139 | Expr::NumLit(s) => self.visit_num_lit(buf, s), |
1140 | Expr::StrLit(s) => self.visit_str_lit(buf, s), |
1141 | Expr::CharLit(s) => self.visit_char_lit(buf, s), |
1142 | Expr::Var(s) => self.visit_var(buf, s), |
1143 | Expr::Path(ref path) => self.visit_path(buf, path), |
1144 | Expr::Array(ref elements) => self.visit_array(buf, elements)?, |
1145 | Expr::Attr(ref obj, name) => self.visit_attr(buf, obj, name)?, |
1146 | Expr::Index(ref obj, ref key) => self.visit_index(buf, obj, key)?, |
1147 | Expr::Filter(name, ref args) => self.visit_filter(buf, name, args)?, |
1148 | Expr::Unary(op, ref inner) => self.visit_unary(buf, op, inner)?, |
1149 | Expr::BinOp(op, ref left, ref right) => self.visit_binop(buf, op, left, right)?, |
1150 | Expr::Range(op, ref left, ref right) => { |
1151 | self.visit_range(buf, op, left.as_deref(), right.as_deref())? |
1152 | } |
1153 | Expr::Group(ref inner) => self.visit_group(buf, inner)?, |
1154 | Expr::Call(ref obj, ref args) => self.visit_call(buf, obj, args)?, |
1155 | Expr::RustMacro(ref path, args) => self.visit_rust_macro(buf, path, args), |
1156 | Expr::Try(ref expr) => self.visit_try(buf, expr.as_ref())?, |
1157 | Expr::Tuple(ref exprs) => self.visit_tuple(buf, exprs)?, |
1158 | Expr::NamedArgument(_, ref expr) => self.visit_named_argument(buf, expr)?, |
1159 | }) |
1160 | } |
1161 | |
1162 | fn visit_try( |
1163 | &mut self, |
1164 | buf: &mut Buffer, |
1165 | expr: &Expr<'_>, |
1166 | ) -> Result<DisplayWrap, CompileError> { |
1167 | buf.write("::core::result::Result::map_err(" ); |
1168 | self.visit_expr(buf, expr)?; |
1169 | buf.write(", |err| ::askama::shared::Error::Custom(::core::convert::Into::into(err)))?" ); |
1170 | Ok(DisplayWrap::Unwrapped) |
1171 | } |
1172 | |
1173 | fn visit_rust_macro(&mut self, buf: &mut Buffer, path: &[&str], args: &str) -> DisplayWrap { |
1174 | self.visit_path(buf, path); |
1175 | buf.write("!(" ); |
1176 | buf.write(args); |
1177 | buf.write(")" ); |
1178 | |
1179 | DisplayWrap::Unwrapped |
1180 | } |
1181 | |
1182 | #[cfg (not(feature = "markdown" ))] |
1183 | fn _visit_markdown_filter( |
1184 | &mut self, |
1185 | _buf: &mut Buffer, |
1186 | _args: &[Expr<'_>], |
1187 | ) -> Result<DisplayWrap, CompileError> { |
1188 | Err("the `markdown` filter requires the `markdown` feature to be enabled" .into()) |
1189 | } |
1190 | |
1191 | #[cfg (feature = "markdown" )] |
1192 | fn _visit_markdown_filter( |
1193 | &mut self, |
1194 | buf: &mut Buffer, |
1195 | args: &[Expr<'_>], |
1196 | ) -> Result<DisplayWrap, CompileError> { |
1197 | let (md, options) = match args { |
1198 | [md] => (md, None), |
1199 | [md, options] => (md, Some(options)), |
1200 | _ => return Err("markdown filter expects no more than one option argument" .into()), |
1201 | }; |
1202 | |
1203 | buf.write(&format!( |
1204 | "::askama::filters::markdown( {}, &" , |
1205 | self.input.escaper |
1206 | )); |
1207 | self.visit_expr(buf, md)?; |
1208 | match options { |
1209 | Some(options) => { |
1210 | buf.write(", ::core::option::Option::Some(" ); |
1211 | self.visit_expr(buf, options)?; |
1212 | buf.write(")" ); |
1213 | } |
1214 | None => buf.write(", ::core::option::Option::None" ), |
1215 | } |
1216 | buf.write(")?" ); |
1217 | |
1218 | Ok(DisplayWrap::Wrapped) |
1219 | } |
1220 | |
1221 | fn _visit_as_ref_filter( |
1222 | &mut self, |
1223 | buf: &mut Buffer, |
1224 | args: &[Expr<'_>], |
1225 | ) -> Result<(), CompileError> { |
1226 | let arg = match args { |
1227 | [arg] => arg, |
1228 | _ => return Err("unexpected argument(s) in `as_ref` filter" .into()), |
1229 | }; |
1230 | buf.write("&" ); |
1231 | self.visit_expr(buf, arg)?; |
1232 | Ok(()) |
1233 | } |
1234 | |
1235 | fn visit_filter( |
1236 | &mut self, |
1237 | buf: &mut Buffer, |
1238 | mut name: &str, |
1239 | args: &[Expr<'_>], |
1240 | ) -> Result<DisplayWrap, CompileError> { |
1241 | if matches!(name, "escape" | "e" ) { |
1242 | self._visit_escape_filter(buf, args)?; |
1243 | return Ok(DisplayWrap::Wrapped); |
1244 | } else if name == "format" { |
1245 | self._visit_format_filter(buf, args)?; |
1246 | return Ok(DisplayWrap::Unwrapped); |
1247 | } else if name == "fmt" { |
1248 | self._visit_fmt_filter(buf, args)?; |
1249 | return Ok(DisplayWrap::Unwrapped); |
1250 | } else if name == "join" { |
1251 | self._visit_join_filter(buf, args)?; |
1252 | return Ok(DisplayWrap::Unwrapped); |
1253 | } else if name == "markdown" { |
1254 | return self._visit_markdown_filter(buf, args); |
1255 | } else if name == "as_ref" { |
1256 | self._visit_as_ref_filter(buf, args)?; |
1257 | return Ok(DisplayWrap::Wrapped); |
1258 | } |
1259 | |
1260 | if name == "tojson" { |
1261 | name = "json" ; |
1262 | } |
1263 | |
1264 | #[cfg (not(feature = "serde-json" ))] |
1265 | if name == "json" { |
1266 | return Err("the `json` filter requires the `serde-json` feature to be enabled" .into()); |
1267 | } |
1268 | #[cfg (not(feature = "serde-yaml" ))] |
1269 | if name == "yaml" { |
1270 | return Err("the `yaml` filter requires the `serde-yaml` feature to be enabled" .into()); |
1271 | } |
1272 | |
1273 | const FILTERS: [&str; 2] = ["safe" , "yaml" ]; |
1274 | if FILTERS.contains(&name) { |
1275 | buf.write(&format!( |
1276 | "::askama::filters:: {}( {}, " , |
1277 | name, self.input.escaper |
1278 | )); |
1279 | } else if crate::BUILT_IN_FILTERS.contains(&name) { |
1280 | buf.write(&format!("::askama::filters:: {name}(" )); |
1281 | } else { |
1282 | buf.write(&format!("filters:: {name}(" )); |
1283 | } |
1284 | |
1285 | self._visit_args(buf, args)?; |
1286 | buf.write(")?" ); |
1287 | Ok(match FILTERS.contains(&name) { |
1288 | true => DisplayWrap::Wrapped, |
1289 | false => DisplayWrap::Unwrapped, |
1290 | }) |
1291 | } |
1292 | |
1293 | fn _visit_escape_filter( |
1294 | &mut self, |
1295 | buf: &mut Buffer, |
1296 | args: &[Expr<'_>], |
1297 | ) -> Result<(), CompileError> { |
1298 | if args.len() > 2 { |
1299 | return Err("only two arguments allowed to escape filter" .into()); |
1300 | } |
1301 | let opt_escaper = match args.get(1) { |
1302 | Some(Expr::StrLit(name)) => Some(*name), |
1303 | Some(_) => return Err("invalid escaper type for escape filter" .into()), |
1304 | None => None, |
1305 | }; |
1306 | let escaper = match opt_escaper { |
1307 | Some(name) => self |
1308 | .input |
1309 | .config |
1310 | .escapers |
1311 | .iter() |
1312 | .find_map(|(escapers, escaper)| escapers.contains(name).then_some(escaper)) |
1313 | .ok_or_else(|| CompileError::from("invalid escaper for escape filter" ))?, |
1314 | None => self.input.escaper, |
1315 | }; |
1316 | buf.write("::askama::filters::escape(" ); |
1317 | buf.write(escaper); |
1318 | buf.write(", " ); |
1319 | self._visit_args(buf, &args[..1])?; |
1320 | buf.write(")?" ); |
1321 | Ok(()) |
1322 | } |
1323 | |
1324 | fn _visit_format_filter( |
1325 | &mut self, |
1326 | buf: &mut Buffer, |
1327 | args: &[Expr<'_>], |
1328 | ) -> Result<(), CompileError> { |
1329 | buf.write("format!(" ); |
1330 | if let Some(Expr::StrLit(v)) = args.first() { |
1331 | self.visit_str_lit(buf, v); |
1332 | if args.len() > 1 { |
1333 | buf.write(", " ); |
1334 | } |
1335 | } else { |
1336 | return Err("invalid expression type for format filter" .into()); |
1337 | } |
1338 | self._visit_args(buf, &args[1..])?; |
1339 | buf.write(")" ); |
1340 | Ok(()) |
1341 | } |
1342 | |
1343 | fn _visit_fmt_filter( |
1344 | &mut self, |
1345 | buf: &mut Buffer, |
1346 | args: &[Expr<'_>], |
1347 | ) -> Result<(), CompileError> { |
1348 | buf.write("format!(" ); |
1349 | if let Some(Expr::StrLit(v)) = args.get(1) { |
1350 | self.visit_str_lit(buf, v); |
1351 | buf.write(", " ); |
1352 | } else { |
1353 | return Err("invalid expression type for fmt filter" .into()); |
1354 | } |
1355 | self._visit_args(buf, &args[0..1])?; |
1356 | if args.len() > 2 { |
1357 | return Err("only two arguments allowed to fmt filter" .into()); |
1358 | } |
1359 | buf.write(")" ); |
1360 | Ok(()) |
1361 | } |
1362 | |
1363 | // Force type coercion on first argument to `join` filter (see #39). |
1364 | fn _visit_join_filter( |
1365 | &mut self, |
1366 | buf: &mut Buffer, |
1367 | args: &[Expr<'_>], |
1368 | ) -> Result<(), CompileError> { |
1369 | buf.write("::askama::filters::join((&" ); |
1370 | for (i, arg) in args.iter().enumerate() { |
1371 | if i > 0 { |
1372 | buf.write(", &" ); |
1373 | } |
1374 | self.visit_expr(buf, arg)?; |
1375 | if i == 0 { |
1376 | buf.write(").into_iter()" ); |
1377 | } |
1378 | } |
1379 | buf.write(")?" ); |
1380 | Ok(()) |
1381 | } |
1382 | |
1383 | fn _visit_args(&mut self, buf: &mut Buffer, args: &[Expr<'_>]) -> Result<(), CompileError> { |
1384 | if args.is_empty() { |
1385 | return Ok(()); |
1386 | } |
1387 | |
1388 | for (i, arg) in args.iter().enumerate() { |
1389 | if i > 0 { |
1390 | buf.write(", " ); |
1391 | } |
1392 | |
1393 | let borrow = !is_copyable(arg); |
1394 | if borrow { |
1395 | buf.write("&(" ); |
1396 | } |
1397 | |
1398 | match arg { |
1399 | Expr::Call(left, _) if !matches!(left.as_ref(), Expr::Path(_)) => { |
1400 | buf.writeln("{" )?; |
1401 | self.visit_expr(buf, arg)?; |
1402 | buf.writeln("}" )?; |
1403 | } |
1404 | _ => { |
1405 | self.visit_expr(buf, arg)?; |
1406 | } |
1407 | } |
1408 | |
1409 | if borrow { |
1410 | buf.write(")" ); |
1411 | } |
1412 | } |
1413 | Ok(()) |
1414 | } |
1415 | |
1416 | fn visit_attr( |
1417 | &mut self, |
1418 | buf: &mut Buffer, |
1419 | obj: &Expr<'_>, |
1420 | attr: &str, |
1421 | ) -> Result<DisplayWrap, CompileError> { |
1422 | if let Expr::Var(name) = *obj { |
1423 | if name == "loop" { |
1424 | if attr == "index" { |
1425 | buf.write("(_loop_item.index + 1)" ); |
1426 | return Ok(DisplayWrap::Unwrapped); |
1427 | } else if attr == "index0" { |
1428 | buf.write("_loop_item.index" ); |
1429 | return Ok(DisplayWrap::Unwrapped); |
1430 | } else if attr == "first" { |
1431 | buf.write("_loop_item.first" ); |
1432 | return Ok(DisplayWrap::Unwrapped); |
1433 | } else if attr == "last" { |
1434 | buf.write("_loop_item.last" ); |
1435 | return Ok(DisplayWrap::Unwrapped); |
1436 | } else { |
1437 | return Err("unknown loop variable" .into()); |
1438 | } |
1439 | } |
1440 | } |
1441 | self.visit_expr(buf, obj)?; |
1442 | buf.write(&format!(". {}" , normalize_identifier(attr))); |
1443 | Ok(DisplayWrap::Unwrapped) |
1444 | } |
1445 | |
1446 | fn visit_index( |
1447 | &mut self, |
1448 | buf: &mut Buffer, |
1449 | obj: &Expr<'_>, |
1450 | key: &Expr<'_>, |
1451 | ) -> Result<DisplayWrap, CompileError> { |
1452 | buf.write("&" ); |
1453 | self.visit_expr(buf, obj)?; |
1454 | buf.write("[" ); |
1455 | self.visit_expr(buf, key)?; |
1456 | buf.write("]" ); |
1457 | Ok(DisplayWrap::Unwrapped) |
1458 | } |
1459 | |
1460 | fn visit_call( |
1461 | &mut self, |
1462 | buf: &mut Buffer, |
1463 | left: &Expr<'_>, |
1464 | args: &[Expr<'_>], |
1465 | ) -> Result<DisplayWrap, CompileError> { |
1466 | match left { |
1467 | Expr::Attr(left, method) if **left == Expr::Var("loop" ) => match *method { |
1468 | "cycle" => match args { |
1469 | [arg] => { |
1470 | if matches!(arg, Expr::Array(arr) if arr.is_empty()) { |
1471 | return Err("loop.cycle(…) cannot use an empty array" .into()); |
1472 | } |
1473 | buf.write("({" ); |
1474 | buf.write("let _cycle = &(" ); |
1475 | self.visit_expr(buf, arg)?; |
1476 | buf.writeln(");" )?; |
1477 | buf.writeln("let _len = _cycle.len();" )?; |
1478 | buf.writeln("if _len == 0 {" )?; |
1479 | buf.writeln("return ::core::result::Result::Err(::askama::Error::Fmt(::core::fmt::Error));" )?; |
1480 | buf.writeln("}" )?; |
1481 | buf.writeln("_cycle[_loop_item.index % _len]" )?; |
1482 | buf.writeln("})" )?; |
1483 | } |
1484 | _ => return Err("loop.cycle(…) expects exactly one argument" .into()), |
1485 | }, |
1486 | s => return Err(format!("unknown loop method: {s:?}" ).into()), |
1487 | }, |
1488 | left => { |
1489 | match left { |
1490 | Expr::Var(name) => match self.locals.resolve(name) { |
1491 | Some(resolved) => buf.write(&resolved), |
1492 | None => buf.write(&format!("(&self. {})" , normalize_identifier(name))), |
1493 | }, |
1494 | left => { |
1495 | self.visit_expr(buf, left)?; |
1496 | } |
1497 | } |
1498 | |
1499 | buf.write("(" ); |
1500 | self._visit_args(buf, args)?; |
1501 | buf.write(")" ); |
1502 | } |
1503 | } |
1504 | Ok(DisplayWrap::Unwrapped) |
1505 | } |
1506 | |
1507 | fn visit_unary( |
1508 | &mut self, |
1509 | buf: &mut Buffer, |
1510 | op: &str, |
1511 | inner: &Expr<'_>, |
1512 | ) -> Result<DisplayWrap, CompileError> { |
1513 | buf.write(op); |
1514 | self.visit_expr(buf, inner)?; |
1515 | Ok(DisplayWrap::Unwrapped) |
1516 | } |
1517 | |
1518 | fn visit_range( |
1519 | &mut self, |
1520 | buf: &mut Buffer, |
1521 | op: &str, |
1522 | left: Option<&Expr<'_>>, |
1523 | right: Option<&Expr<'_>>, |
1524 | ) -> Result<DisplayWrap, CompileError> { |
1525 | if let Some(left) = left { |
1526 | self.visit_expr(buf, left)?; |
1527 | } |
1528 | buf.write(op); |
1529 | if let Some(right) = right { |
1530 | self.visit_expr(buf, right)?; |
1531 | } |
1532 | Ok(DisplayWrap::Unwrapped) |
1533 | } |
1534 | |
1535 | fn visit_binop( |
1536 | &mut self, |
1537 | buf: &mut Buffer, |
1538 | op: &str, |
1539 | left: &Expr<'_>, |
1540 | right: &Expr<'_>, |
1541 | ) -> Result<DisplayWrap, CompileError> { |
1542 | self.visit_expr(buf, left)?; |
1543 | buf.write(&format!(" {op} " )); |
1544 | self.visit_expr(buf, right)?; |
1545 | Ok(DisplayWrap::Unwrapped) |
1546 | } |
1547 | |
1548 | fn visit_group( |
1549 | &mut self, |
1550 | buf: &mut Buffer, |
1551 | inner: &Expr<'_>, |
1552 | ) -> Result<DisplayWrap, CompileError> { |
1553 | buf.write("(" ); |
1554 | self.visit_expr(buf, inner)?; |
1555 | buf.write(")" ); |
1556 | Ok(DisplayWrap::Unwrapped) |
1557 | } |
1558 | |
1559 | fn visit_tuple( |
1560 | &mut self, |
1561 | buf: &mut Buffer, |
1562 | exprs: &[Expr<'_>], |
1563 | ) -> Result<DisplayWrap, CompileError> { |
1564 | buf.write("(" ); |
1565 | for (index, expr) in exprs.iter().enumerate() { |
1566 | if index > 0 { |
1567 | buf.write(" " ); |
1568 | } |
1569 | self.visit_expr(buf, expr)?; |
1570 | buf.write("," ); |
1571 | } |
1572 | buf.write(")" ); |
1573 | Ok(DisplayWrap::Unwrapped) |
1574 | } |
1575 | |
1576 | fn visit_named_argument( |
1577 | &mut self, |
1578 | buf: &mut Buffer, |
1579 | expr: &Expr<'_>, |
1580 | ) -> Result<DisplayWrap, CompileError> { |
1581 | self.visit_expr(buf, expr)?; |
1582 | Ok(DisplayWrap::Unwrapped) |
1583 | } |
1584 | |
1585 | fn visit_array( |
1586 | &mut self, |
1587 | buf: &mut Buffer, |
1588 | elements: &[Expr<'_>], |
1589 | ) -> Result<DisplayWrap, CompileError> { |
1590 | buf.write("[" ); |
1591 | for (i, el) in elements.iter().enumerate() { |
1592 | if i > 0 { |
1593 | buf.write(", " ); |
1594 | } |
1595 | self.visit_expr(buf, el)?; |
1596 | } |
1597 | buf.write("]" ); |
1598 | Ok(DisplayWrap::Unwrapped) |
1599 | } |
1600 | |
1601 | fn visit_path(&mut self, buf: &mut Buffer, path: &[&str]) -> DisplayWrap { |
1602 | for (i, part) in path.iter().enumerate() { |
1603 | if i > 0 { |
1604 | buf.write("::" ); |
1605 | } |
1606 | buf.write(part); |
1607 | } |
1608 | DisplayWrap::Unwrapped |
1609 | } |
1610 | |
1611 | fn visit_var(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { |
1612 | if s == "self" { |
1613 | buf.write(s); |
1614 | return DisplayWrap::Unwrapped; |
1615 | } |
1616 | |
1617 | buf.write(normalize_identifier(&self.locals.resolve_or_self(s))); |
1618 | DisplayWrap::Unwrapped |
1619 | } |
1620 | |
1621 | fn visit_bool_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { |
1622 | buf.write(s); |
1623 | DisplayWrap::Unwrapped |
1624 | } |
1625 | |
1626 | fn visit_str_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { |
1627 | buf.write(&format!(" \"{s}\"" )); |
1628 | DisplayWrap::Unwrapped |
1629 | } |
1630 | |
1631 | fn visit_char_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { |
1632 | buf.write(&format!("' {s}'" )); |
1633 | DisplayWrap::Unwrapped |
1634 | } |
1635 | |
1636 | fn visit_num_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { |
1637 | buf.write(s); |
1638 | DisplayWrap::Unwrapped |
1639 | } |
1640 | |
1641 | fn visit_target( |
1642 | &mut self, |
1643 | buf: &mut Buffer, |
1644 | initialized: bool, |
1645 | first_level: bool, |
1646 | target: &Target<'a>, |
1647 | ) { |
1648 | match target { |
1649 | Target::Name("_" ) => { |
1650 | buf.write("_" ); |
1651 | } |
1652 | Target::Name(name) => { |
1653 | let name = normalize_identifier(name); |
1654 | match initialized { |
1655 | true => self.locals.insert(name, LocalMeta::initialized()), |
1656 | false => self.locals.insert_with_default(name), |
1657 | } |
1658 | buf.write(name); |
1659 | } |
1660 | Target::OrChain(targets) => match targets.first() { |
1661 | None => buf.write("_" ), |
1662 | Some(first_target) => { |
1663 | self.visit_target(buf, initialized, first_level, first_target); |
1664 | for target in &targets[1..] { |
1665 | buf.write(" | " ); |
1666 | self.visit_target(buf, initialized, first_level, target); |
1667 | } |
1668 | } |
1669 | }, |
1670 | Target::Tuple(path, targets) => { |
1671 | buf.write(&path.join("::" )); |
1672 | buf.write("(" ); |
1673 | for target in targets { |
1674 | self.visit_target(buf, initialized, false, target); |
1675 | buf.write("," ); |
1676 | } |
1677 | buf.write(")" ); |
1678 | } |
1679 | Target::Struct(path, targets) => { |
1680 | buf.write(&path.join("::" )); |
1681 | buf.write(" { " ); |
1682 | for (name, target) in targets { |
1683 | buf.write(normalize_identifier(name)); |
1684 | buf.write(": " ); |
1685 | self.visit_target(buf, initialized, false, target); |
1686 | buf.write("," ); |
1687 | } |
1688 | buf.write(" }" ); |
1689 | } |
1690 | Target::Path(path) => { |
1691 | self.visit_path(buf, path); |
1692 | } |
1693 | Target::StrLit(s) => { |
1694 | if first_level { |
1695 | buf.write("&" ); |
1696 | } |
1697 | self.visit_str_lit(buf, s); |
1698 | } |
1699 | Target::NumLit(s) => { |
1700 | if first_level { |
1701 | buf.write("&" ); |
1702 | } |
1703 | self.visit_num_lit(buf, s); |
1704 | } |
1705 | Target::CharLit(s) => { |
1706 | if first_level { |
1707 | buf.write("&" ); |
1708 | } |
1709 | self.visit_char_lit(buf, s); |
1710 | } |
1711 | Target::BoolLit(s) => { |
1712 | if first_level { |
1713 | buf.write("&" ); |
1714 | } |
1715 | buf.write(s); |
1716 | } |
1717 | } |
1718 | } |
1719 | |
1720 | /* Helper methods for dealing with whitespace nodes */ |
1721 | |
1722 | // Combines `flush_ws()` and `prepare_ws()` to handle both trailing whitespace from the |
1723 | // preceding literal and leading whitespace from the succeeding literal. |
1724 | fn handle_ws(&mut self, ws: Ws) { |
1725 | self.flush_ws(ws); |
1726 | self.prepare_ws(ws); |
1727 | } |
1728 | |
1729 | fn should_trim_ws(&self, ws: Option<Whitespace>) -> WhitespaceHandling { |
1730 | match ws { |
1731 | Some(Whitespace::Suppress) => WhitespaceHandling::Suppress, |
1732 | Some(Whitespace::Preserve) => WhitespaceHandling::Preserve, |
1733 | Some(Whitespace::Minimize) => WhitespaceHandling::Minimize, |
1734 | None => self.input.config.whitespace, |
1735 | } |
1736 | } |
1737 | |
1738 | // If the previous literal left some trailing whitespace in `next_ws` and the |
1739 | // prefix whitespace suppressor from the given argument, flush that whitespace. |
1740 | // In either case, `next_ws` is reset to `None` (no trailing whitespace). |
1741 | fn flush_ws(&mut self, ws: Ws) { |
1742 | if self.next_ws.is_none() { |
1743 | return; |
1744 | } |
1745 | |
1746 | // If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is |
1747 | // a `+` character. |
1748 | match self.should_trim_ws(ws.0) { |
1749 | WhitespaceHandling::Preserve => { |
1750 | let val = self.next_ws.unwrap(); |
1751 | if !val.is_empty() { |
1752 | self.buf_writable.push(Writable::Lit(val)); |
1753 | } |
1754 | } |
1755 | WhitespaceHandling::Minimize => { |
1756 | let val = self.next_ws.unwrap(); |
1757 | if !val.is_empty() { |
1758 | self.buf_writable |
1759 | .push(Writable::Lit(match val.contains(' \n' ) { |
1760 | true => " \n" , |
1761 | false => " " , |
1762 | })); |
1763 | } |
1764 | } |
1765 | WhitespaceHandling::Suppress => {} |
1766 | } |
1767 | self.next_ws = None; |
1768 | } |
1769 | |
1770 | // Sets `skip_ws` to match the suffix whitespace suppressor from the given |
1771 | // argument, to determine whether to suppress leading whitespace from the |
1772 | // next literal. |
1773 | fn prepare_ws(&mut self, ws: Ws) { |
1774 | self.skip_ws = self.should_trim_ws(ws.1); |
1775 | } |
1776 | } |
1777 | |
1778 | struct Buffer { |
1779 | // The buffer to generate the code into |
1780 | buf: String, |
1781 | // The current level of indentation (in spaces) |
1782 | indent: u8, |
1783 | // Whether the output buffer is currently at the start of a line |
1784 | start: bool, |
1785 | } |
1786 | |
1787 | impl Buffer { |
1788 | fn new(indent: u8) -> Self { |
1789 | Self { |
1790 | buf: String::new(), |
1791 | indent, |
1792 | start: true, |
1793 | } |
1794 | } |
1795 | |
1796 | fn writeln(&mut self, s: &str) -> Result<(), CompileError> { |
1797 | if s == "}" { |
1798 | self.dedent()?; |
1799 | } |
1800 | if !s.is_empty() { |
1801 | self.write(s); |
1802 | } |
1803 | self.buf.push(' \n' ); |
1804 | if s.ends_with('{' ) { |
1805 | self.indent(); |
1806 | } |
1807 | self.start = true; |
1808 | Ok(()) |
1809 | } |
1810 | |
1811 | fn write(&mut self, s: &str) { |
1812 | if self.start { |
1813 | for _ in 0..(self.indent * 4) { |
1814 | self.buf.push(' ' ); |
1815 | } |
1816 | self.start = false; |
1817 | } |
1818 | self.buf.push_str(s); |
1819 | } |
1820 | |
1821 | fn indent(&mut self) { |
1822 | self.indent += 1; |
1823 | } |
1824 | |
1825 | fn dedent(&mut self) -> Result<(), CompileError> { |
1826 | if self.indent == 0 { |
1827 | return Err("dedent() called while indentation == 0" .into()); |
1828 | } |
1829 | self.indent -= 1; |
1830 | Ok(()) |
1831 | } |
1832 | } |
1833 | |
1834 | #[derive (Clone, Default)] |
1835 | pub(crate) struct LocalMeta { |
1836 | refs: Option<String>, |
1837 | initialized: bool, |
1838 | } |
1839 | |
1840 | impl LocalMeta { |
1841 | fn initialized() -> Self { |
1842 | Self { |
1843 | refs: None, |
1844 | initialized: true, |
1845 | } |
1846 | } |
1847 | |
1848 | fn with_ref(refs: String) -> Self { |
1849 | Self { |
1850 | refs: Some(refs), |
1851 | initialized: true, |
1852 | } |
1853 | } |
1854 | } |
1855 | |
1856 | // type SetChain<'a, T> = MapChain<'a, T, ()>; |
1857 | |
1858 | #[derive (Debug)] |
1859 | pub(crate) struct MapChain<'a, K, V> |
1860 | where |
1861 | K: cmp::Eq + hash::Hash, |
1862 | { |
1863 | parent: Option<&'a MapChain<'a, K, V>>, |
1864 | scopes: Vec<HashMap<K, V>>, |
1865 | } |
1866 | |
1867 | impl<'a, K: 'a, V: 'a> MapChain<'a, K, V> |
1868 | where |
1869 | K: cmp::Eq + hash::Hash, |
1870 | { |
1871 | fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> { |
1872 | MapChain { |
1873 | parent: Some(parent), |
1874 | scopes: vec![HashMap::new()], |
1875 | } |
1876 | } |
1877 | |
1878 | /// Iterates the scopes in reverse and returns `Some(LocalMeta)` |
1879 | /// from the first scope where `key` exists. |
1880 | fn get(&self, key: &K) -> Option<&V> { |
1881 | let mut scopes = self.scopes.iter().rev(); |
1882 | scopes |
1883 | .find_map(|set| set.get(key)) |
1884 | .or_else(|| self.parent.and_then(|set| set.get(key))) |
1885 | } |
1886 | |
1887 | fn is_current_empty(&self) -> bool { |
1888 | self.scopes.last().unwrap().is_empty() |
1889 | } |
1890 | |
1891 | fn insert(&mut self, key: K, val: V) { |
1892 | self.scopes.last_mut().unwrap().insert(key, val); |
1893 | |
1894 | // Note that if `insert` returns `Some` then it implies |
1895 | // an identifier is reused. For e.g. `{% macro f(a, a) %}` |
1896 | // and `{% let (a, a) = ... %}` then this results in a |
1897 | // generated template, which when compiled fails with the |
1898 | // compile error "identifier `a` used more than once". |
1899 | } |
1900 | |
1901 | fn insert_with_default(&mut self, key: K) |
1902 | where |
1903 | V: Default, |
1904 | { |
1905 | self.insert(key, V::default()); |
1906 | } |
1907 | |
1908 | fn push(&mut self) { |
1909 | self.scopes.push(HashMap::new()); |
1910 | } |
1911 | |
1912 | fn pop(&mut self) { |
1913 | self.scopes.pop().unwrap(); |
1914 | assert!(!self.scopes.is_empty()); |
1915 | } |
1916 | } |
1917 | |
1918 | impl MapChain<'_, &str, LocalMeta> { |
1919 | fn resolve(&self, name: &str) -> Option<String> { |
1920 | let name: &str = normalize_identifier(ident:name); |
1921 | self.get(&name).map(|meta: &LocalMeta| match &meta.refs { |
1922 | Some(expr: &String) => expr.clone(), |
1923 | None => name.to_string(), |
1924 | }) |
1925 | } |
1926 | |
1927 | fn resolve_or_self(&self, name: &str) -> String { |
1928 | let name: &str = normalize_identifier(ident:name); |
1929 | self.resolve(name).unwrap_or_else(|| format!("self. {name}" )) |
1930 | } |
1931 | } |
1932 | |
1933 | impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> { |
1934 | fn default() -> Self { |
1935 | Self { |
1936 | parent: None, |
1937 | scopes: vec![HashMap::new()], |
1938 | } |
1939 | } |
1940 | } |
1941 | |
1942 | /// Returns `true` if enough assumptions can be made, |
1943 | /// to determine that `self` is copyable. |
1944 | fn is_copyable(expr: &Expr<'_>) -> bool { |
1945 | is_copyable_within_op(expr, within_op:false) |
1946 | } |
1947 | |
1948 | fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool { |
1949 | use Expr::*; |
1950 | match expr { |
1951 | BoolLit(_) | NumLit(_) | StrLit(_) | CharLit(_) => true, |
1952 | Unary(.., expr: &Box>) => is_copyable_within_op(expr, within_op:true), |
1953 | BinOp(_, lhs: &Box>, rhs: &Box>) => is_copyable_within_op(expr:lhs, within_op:true) && is_copyable_within_op(expr:rhs, within_op:true), |
1954 | Range(..) => true, |
1955 | // The result of a call likely doesn't need to be borrowed, |
1956 | // as in that case the call is more likely to return a |
1957 | // reference in the first place then. |
1958 | Call(..) | Path(..) => true, |
1959 | // If the `expr` is within a `Unary` or `BinOp` then |
1960 | // an assumption can be made that the operand is copy. |
1961 | // If not, then the value is moved and adding `.clone()` |
1962 | // will solve that issue. However, if the operand is |
1963 | // implicitly borrowed, then it's likely not even possible |
1964 | // to get the template to compile. |
1965 | _ => within_op && is_attr_self(expr), |
1966 | } |
1967 | } |
1968 | |
1969 | /// Returns `true` if this is an `Attr` where the `obj` is `"self"`. |
1970 | pub(crate) fn is_attr_self(expr: &Expr<'_>) -> bool { |
1971 | match expr { |
1972 | Expr::Attr(obj: &Box>, _) if matches!(obj.as_ref(), Expr::Var("self" )) => true, |
1973 | Expr::Attr(obj: &Box>, _) if matches!(obj.as_ref(), Expr::Attr(..)) => is_attr_self(expr:obj), |
1974 | _ => false, |
1975 | } |
1976 | } |
1977 | |
1978 | /// Returns `true` if the outcome of this expression may be used multiple times in the same |
1979 | /// `write!()` call, without evaluating the expression again, i.e. the expression should be |
1980 | /// side-effect free. |
1981 | pub(crate) fn is_cacheable(expr: &Expr<'_>) -> bool { |
1982 | match expr { |
1983 | // Literals are the definition of pure: |
1984 | Expr::BoolLit(_) => true, |
1985 | Expr::NumLit(_) => true, |
1986 | Expr::StrLit(_) => true, |
1987 | Expr::CharLit(_) => true, |
1988 | // fmt::Display should have no effects: |
1989 | Expr::Var(_) => true, |
1990 | Expr::Path(_) => true, |
1991 | // Check recursively: |
1992 | Expr::Array(args) => args.iter().all(is_cacheable), |
1993 | Expr::Attr(lhs, _) => is_cacheable(lhs), |
1994 | Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), |
1995 | Expr::Filter(_, args) => args.iter().all(is_cacheable), |
1996 | Expr::Unary(_, arg) => is_cacheable(arg), |
1997 | Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), |
1998 | Expr::Range(_, lhs, rhs) => { |
1999 | lhs.as_ref().map_or(true, |v| is_cacheable(v)) |
2000 | && rhs.as_ref().map_or(true, |v| is_cacheable(v)) |
2001 | } |
2002 | Expr::Group(arg) => is_cacheable(arg), |
2003 | Expr::Tuple(args) => args.iter().all(is_cacheable), |
2004 | Expr::NamedArgument(_, expr) => is_cacheable(expr), |
2005 | // We have too little information to tell if the expression is pure: |
2006 | Expr::Call(_, _) => false, |
2007 | Expr::RustMacro(_, _) => false, |
2008 | Expr::Try(_) => false, |
2009 | } |
2010 | } |
2011 | |
2012 | fn median(sizes: &mut [usize]) -> usize { |
2013 | sizes.sort_unstable(); |
2014 | if sizes.len() % 2 == 1 { |
2015 | sizes[sizes.len() / 2] |
2016 | } else { |
2017 | (sizes[sizes.len() / 2 - 1] + sizes[sizes.len() / 2]) / 2 |
2018 | } |
2019 | } |
2020 | |
2021 | #[derive (Clone, Copy, PartialEq)] |
2022 | enum AstLevel { |
2023 | Top, |
2024 | Block, |
2025 | Nested, |
2026 | } |
2027 | |
2028 | #[derive (Clone, Copy)] |
2029 | enum DisplayWrap { |
2030 | Wrapped, |
2031 | Unwrapped, |
2032 | } |
2033 | |
2034 | #[derive (Debug)] |
2035 | enum Writable<'a> { |
2036 | Lit(&'a str), |
2037 | Expr(&'a Expr<'a>), |
2038 | } |
2039 | |
2040 | // Identifiers to be replaced with raw identifiers, so as to avoid |
2041 | // collisions between template syntax and Rust's syntax. In particular |
2042 | // [Rust keywords](https://doc.rust-lang.org/reference/keywords.html) |
2043 | // should be replaced, since they're not reserved words in Askama |
2044 | // syntax but have a high probability of causing problems in the |
2045 | // generated code. |
2046 | // |
2047 | // This list excludes the Rust keywords *self*, *Self*, and *super* |
2048 | // because they are not allowed to be raw identifiers, and *loop* |
2049 | // because it's used something like a keyword in the template |
2050 | // language. |
2051 | fn normalize_identifier(ident: &str) -> &str { |
2052 | // This table works for as long as the replacement string is the original string |
2053 | // prepended with "r#". The strings get right-padded to the same length with b'_'. |
2054 | // While the code does not need it, please keep the list sorted when adding new |
2055 | // keywords. |
2056 | |
2057 | // FIXME: Replace with `[core:ascii::Char; MAX_REPL_LEN]` once |
2058 | // <https://github.com/rust-lang/rust/issues/110998> is stable. |
2059 | |
2060 | const MAX_KW_LEN: usize = 8; |
2061 | const MAX_REPL_LEN: usize = MAX_KW_LEN + 2; |
2062 | |
2063 | const KW0: &[[u8; MAX_REPL_LEN]] = &[]; |
2064 | const KW1: &[[u8; MAX_REPL_LEN]] = &[]; |
2065 | const KW2: &[[u8; MAX_REPL_LEN]] = &[ |
2066 | *b"r#as______" , |
2067 | *b"r#do______" , |
2068 | *b"r#fn______" , |
2069 | *b"r#if______" , |
2070 | *b"r#in______" , |
2071 | ]; |
2072 | const KW3: &[[u8; MAX_REPL_LEN]] = &[ |
2073 | *b"r#box_____" , |
2074 | *b"r#dyn_____" , |
2075 | *b"r#for_____" , |
2076 | *b"r#let_____" , |
2077 | *b"r#mod_____" , |
2078 | *b"r#mut_____" , |
2079 | *b"r#pub_____" , |
2080 | *b"r#ref_____" , |
2081 | *b"r#try_____" , |
2082 | *b"r#use_____" , |
2083 | ]; |
2084 | const KW4: &[[u8; MAX_REPL_LEN]] = &[ |
2085 | *b"r#else____" , |
2086 | *b"r#enum____" , |
2087 | *b"r#impl____" , |
2088 | *b"r#move____" , |
2089 | *b"r#priv____" , |
2090 | *b"r#true____" , |
2091 | *b"r#type____" , |
2092 | ]; |
2093 | const KW5: &[[u8; MAX_REPL_LEN]] = &[ |
2094 | *b"r#async___" , |
2095 | *b"r#await___" , |
2096 | *b"r#break___" , |
2097 | *b"r#const___" , |
2098 | *b"r#crate___" , |
2099 | *b"r#false___" , |
2100 | *b"r#final___" , |
2101 | *b"r#macro___" , |
2102 | *b"r#match___" , |
2103 | *b"r#trait___" , |
2104 | *b"r#where___" , |
2105 | *b"r#while___" , |
2106 | *b"r#yield___" , |
2107 | ]; |
2108 | const KW6: &[[u8; MAX_REPL_LEN]] = &[ |
2109 | *b"r#become__" , |
2110 | *b"r#extern__" , |
2111 | *b"r#return__" , |
2112 | *b"r#static__" , |
2113 | *b"r#struct__" , |
2114 | *b"r#typeof__" , |
2115 | *b"r#unsafe__" , |
2116 | ]; |
2117 | const KW7: &[[u8; MAX_REPL_LEN]] = &[*b"r#unsized_" , *b"r#virtual_" ]; |
2118 | const KW8: &[[u8; MAX_REPL_LEN]] = &[*b"r#abstract" , *b"r#continue" , *b"r#override" ]; |
2119 | |
2120 | const KWS: &[&[[u8; MAX_REPL_LEN]]] = &[KW0, KW1, KW2, KW3, KW4, KW5, KW6, KW7, KW8]; |
2121 | |
2122 | // Ensure that all strings are ASCII, because we use `from_utf8_unchecked()` further down. |
2123 | const _: () = { |
2124 | let mut i = 0; |
2125 | while i < KWS.len() { |
2126 | let mut j = 0; |
2127 | while KWS[i].len() < j { |
2128 | let mut k = 0; |
2129 | while KWS[i][j].len() < k { |
2130 | assert!(KWS[i][j][k].is_ascii()); |
2131 | k += 1; |
2132 | } |
2133 | j += 1; |
2134 | } |
2135 | i += 1; |
2136 | } |
2137 | }; |
2138 | |
2139 | if ident.len() > MAX_KW_LEN { |
2140 | return ident; |
2141 | } |
2142 | let kws = KWS[ident.len()]; |
2143 | |
2144 | let mut padded_ident = [b'_' ; MAX_KW_LEN]; |
2145 | padded_ident[..ident.len()].copy_from_slice(ident.as_bytes()); |
2146 | |
2147 | // Since the individual buckets are quite short, a linear search is faster than a binary search. |
2148 | let replacement = match kws |
2149 | .iter() |
2150 | .find(|probe| padded_ident == <[u8; MAX_KW_LEN]>::try_from(&probe[2..]).unwrap()) |
2151 | { |
2152 | Some(replacement) => replacement, |
2153 | None => return ident, |
2154 | }; |
2155 | |
2156 | // SAFETY: We know that the input byte slice is pure-ASCII. |
2157 | unsafe { std::str::from_utf8_unchecked(&replacement[..ident.len() + 2]) } |
2158 | } |
2159 | |