From ec8301d431d53bc6e590203004d6d43275ca6902 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 02:29:11 -0500 Subject: [PATCH 01/27] Add test for parsing Node::Call --- askama_derive/src/parser/tests.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 91bb09ba8..86c821c0d 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -666,3 +666,28 @@ fn test_missing_space_after_kw() { "unable to parse template:\n\n\"{%leta=b%}\"" )); } + +#[test] +fn test_parse_call_statement() { + let syntax = Syntax::default(); + assert_eq!( + super::parse("{% call foo(bar) %}", &syntax).unwrap(), + vec![Node::Call( + Ws(None, None), + None, + "foo", + vec![ + Expr::Var("bar"), + ], + )], + ); + assert_eq!( + super::parse("{% call foo::bar() %}", &syntax).unwrap(), + vec![Node::Call( + Ws(None, None), + Some("foo"), + "bar", + vec![], + )], + ); +} From fcb01b6bb09470116b0b310933fdb921a26be340 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 02:32:07 -0500 Subject: [PATCH 02/27] Extract Call struct from Node --- askama_derive/src/generator.rs | 11 +++++++++-- askama_derive/src/parser/mod.rs | 4 +++- askama_derive/src/parser/node.rs | 15 +++++++++++++-- askama_derive/src/parser/tests.rs | 19 +++++++++++-------- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index cdf5ebf75..e53d7c303 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,7 +1,7 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; use crate::input::{Print, Source, TemplateInput}; -use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Whitespace, Ws}; +use crate::parser::{parse, Call, Cond, CondTest, Expr, Loop, Node, Target, When, Whitespace, Ws}; use crate::CompileError; use proc_macro::TokenStream; @@ -665,7 +665,14 @@ impl<'a> Generator<'a> { Node::Include(ws, path) => { size_hint += self.handle_include(ctx, buf, ws, path)?; } - Node::Call(ws, scope, name, ref args) => { + Node::Call( + ws, + Call { + scope, + name, + ref args, + }, + ) => { size_hint += self.write_call(ctx, buf, ws, scope, name, args)?; } Node::Macro(_, ref m) => { diff --git a/askama_derive/src/parser/mod.rs b/askama_derive/src/parser/mod.rs index 79b178ef8..7f0df9159 100644 --- a/askama_derive/src/parser/mod.rs +++ b/askama_derive/src/parser/mod.rs @@ -12,7 +12,9 @@ use nom::sequence::{delimited, pair, tuple}; use nom::{error_position, AsChar, IResult, InputTakeAtPosition}; pub(crate) use self::expr::Expr; -pub(crate) use self::node::{Cond, CondTest, Loop, Macro, Node, Target, When, Whitespace, Ws}; +pub(crate) use self::node::{ + Call, Cond, CondTest, Loop, Macro, Node, Target, When, Whitespace, Ws, +}; use crate::config::Syntax; use crate::CompileError; diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index 8c122af5f..35e0b19cc 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -21,7 +21,7 @@ pub(crate) enum Node<'a> { Lit(&'a str, &'a str, &'a str), Comment(Ws), Expr(Ws, Expr<'a>), - Call(Ws, Option<&'a str>, &'a str, Vec>), + Call(Ws, Call<'a>), LetDecl(Ws, Target<'a>), Let(Ws, Target<'a>, Expr<'a>), Cond(Vec>, Ws), @@ -66,6 +66,17 @@ impl From for Whitespace { } } +/// A macro call statement. +#[derive(Debug, PartialEq)] +pub(crate) struct Call<'a> { + /// If the macro is imported, the scope name. + pub(crate) scope: Option<&'a str>, + /// The name of the macro to call. + pub(crate) name: &'a str, + /// The arguments to the macro. + pub(crate) args: Vec>, +} + #[derive(Debug, PartialEq)] pub(crate) struct Loop<'a> { pub(crate) ws1: Ws, @@ -140,7 +151,7 @@ fn block_call(i: &str) -> IResult<&str, Node<'_>> { let (i, (pws, _, (scope, name, args, nws))) = p(i)?; let scope = scope.map(|(scope, _)| scope); let args = args.unwrap_or_default(); - Ok((i, Node::Call(Ws(pws, nws), scope, name, args))) + Ok((i, Node::Call(Ws(pws, nws), Call { scope, name, args }))) } fn cond_if(i: &str) -> IResult<&str, CondTest<'_>> { diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 86c821c0d..b00ed7f31 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -669,25 +669,28 @@ fn test_missing_space_after_kw() { #[test] fn test_parse_call_statement() { + use super::Call; let syntax = Syntax::default(); assert_eq!( super::parse("{% call foo(bar) %}", &syntax).unwrap(), vec![Node::Call( Ws(None, None), - None, - "foo", - vec![ - Expr::Var("bar"), - ], + Call { + scope: None, + name: "foo", + args: vec![Expr::Var("bar"),], + } )], ); assert_eq!( super::parse("{% call foo::bar() %}", &syntax).unwrap(), vec![Node::Call( Ws(None, None), - Some("foo"), - "bar", - vec![], + Call { + scope: Some("foo"), + name: "bar", + args: vec![], + } )], ); } From 586c770a98db19ae1bf8382679a7f94269cb5fcc Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 02:38:34 -0500 Subject: [PATCH 03/27] Extract Match struct from Node --- askama_derive/src/generator.rs | 15 ++++++++++++--- askama_derive/src/heritage.rs | 4 ++-- askama_derive/src/parser/mod.rs | 2 +- askama_derive/src/parser/node.rs | 15 +++++++++++++-- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index e53d7c303..c3b40219f 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,7 +1,9 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHandling}; use crate::heritage::{Context, Heritage}; use crate::input::{Print, Source, TemplateInput}; -use crate::parser::{parse, Call, Cond, CondTest, Expr, Loop, Node, Target, When, Whitespace, Ws}; +use crate::parser::{ + parse, Call, Cond, CondTest, Expr, Loop, Match, Node, Target, When, Whitespace, Ws, +}; use crate::CompileError; use proc_macro::TokenStream; @@ -653,8 +655,15 @@ impl<'a> Generator<'a> { Node::Cond(ref conds, ws) => { size_hint += self.write_cond(ctx, buf, conds, ws)?; } - Node::Match(ws1, ref expr, ref arms, ws2) => { - size_hint += self.write_match(ctx, buf, ws1, expr, arms, ws2)?; + Node::Match( + ws1, + Match { + ref expr, + ref arms, + ws, + }, + ) => { + size_hint += self.write_match(ctx, buf, ws1, expr, arms, ws)?; } Node::Loop(ref loop_block) => { size_hint += self.write_loop(ctx, buf, loop_block)?; diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index dbb2b1fa0..9b448465e 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use crate::config::Config; -use crate::parser::{Loop, Macro, Node}; +use crate::parser::{Loop, Macro, Match, Node}; use crate::CompileError; pub(crate) struct Heritage<'a> { @@ -93,7 +93,7 @@ impl Context<'_> { nested.push(body); nested.push(else_block); } - Node::Match(_, _, arms, _) => { + Node::Match(_, Match { arms, .. }) => { for (_, _, arm) in arms { nested.push(arm); } diff --git a/askama_derive/src/parser/mod.rs b/askama_derive/src/parser/mod.rs index 7f0df9159..29e8191e3 100644 --- a/askama_derive/src/parser/mod.rs +++ b/askama_derive/src/parser/mod.rs @@ -13,7 +13,7 @@ use nom::{error_position, AsChar, IResult, InputTakeAtPosition}; pub(crate) use self::expr::Expr; pub(crate) use self::node::{ - Call, Cond, CondTest, Loop, Macro, Node, Target, When, Whitespace, Ws, + Call, Cond, CondTest, Loop, Macro, Match, Node, Target, When, Whitespace, Ws, }; use crate::config::Syntax; use crate::CompileError; diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index 35e0b19cc..67f1638c6 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -25,7 +25,7 @@ pub(crate) enum Node<'a> { LetDecl(Ws, Target<'a>), Let(Ws, Target<'a>, Expr<'a>), Cond(Vec>, Ws), - Match(Ws, Expr<'a>, Vec>, Ws), + Match(Ws, Match<'a>), Loop(Loop<'a>), Extends(&'a str), BlockDef(Ws, &'a str, Vec>, Ws), @@ -77,6 +77,16 @@ pub(crate) struct Call<'a> { pub(crate) args: Vec>, } +/// A match statement. +#[derive(Debug, PartialEq)] +pub(crate) struct Match<'a> { + /// The expression to match against. + pub(crate) expr: Expr<'a>, + /// Each of the match arms, with a pattern and a body. + pub(crate) arms: Vec>, + pub(crate) ws: Ws, +} + #[derive(Debug, PartialEq)] pub(crate) struct Loop<'a> { pub(crate) ws1: Ws, @@ -267,13 +277,14 @@ fn block_match<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { ))), )); let (i, (pws1, _, (expr, nws1, _, (_, arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?; + let ws = Ws(pws2, nws2); let mut arms = arms; if let Some(arm) = else_arm { arms.push(arm); } - Ok((i, Node::Match(Ws(pws1, nws1), expr, arms, Ws(pws2, nws2)))) + Ok((i, Node::Match(Ws(pws1, nws1), Match { expr, arms, ws }))) } fn block_let(i: &str) -> IResult<&str, Node<'_>> { From a31b7addb2e590ab9e663fe237dfbac4f90f322e Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 02:46:30 -0500 Subject: [PATCH 04/27] Make When a proper struct --- askama_derive/src/generator.rs | 6 +++--- askama_derive/src/heritage.rs | 6 +++--- askama_derive/src/parser/node.rs | 30 ++++++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index c3b40219f..255060aaa 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -821,8 +821,8 @@ impl<'a> Generator<'a> { let mut arm_size = 0; for (i, arm) in arms.iter().enumerate() { - let &(ws, ref target, ref body) = arm; - self.handle_ws(ws); + let When { ws, target, block } = arm; + self.handle_ws(*ws); if i > 0 { arm_sizes.push(arm_size + self.write_buf_writable(buf)?); @@ -835,7 +835,7 @@ impl<'a> Generator<'a> { self.visit_target(buf, true, true, target); buf.writeln(" => {")?; - arm_size = self.handle(ctx, body, buf, AstLevel::Nested)?; + arm_size = self.handle(ctx, block, buf, AstLevel::Nested)?; } self.handle_ws(ws2); diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 9b448465e..2624162c0 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use crate::config::Config; -use crate::parser::{Loop, Macro, Match, Node}; +use crate::parser::{Loop, Macro, Match, Node, When}; use crate::CompileError; pub(crate) struct Heritage<'a> { @@ -94,8 +94,8 @@ impl Context<'_> { nested.push(else_block); } Node::Match(_, Match { arms, .. }) => { - for (_, _, arm) in arms { - nested.push(arm); + for When { block, .. } in arms { + nested.push(block); } } _ => {} diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index 67f1638c6..8bede5d81 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -87,6 +87,16 @@ pub(crate) struct Match<'a> { pub(crate) ws: Ws, } +/// A single arm of a match statement. +#[derive(Debug, PartialEq)] +pub(crate) struct When<'a> { + pub(crate) ws: Ws, + /// The target pattern to match. + pub(crate) target: Target<'a>, + /// Body of the match arm. + pub(crate) block: Vec>, +} + #[derive(Debug, PartialEq)] pub(crate) struct Loop<'a> { pub(crate) ws1: Ws, @@ -99,8 +109,6 @@ pub(crate) struct Loop<'a> { pub(crate) ws3: Ws, } -pub(crate) type When<'a> = (Ws, Target<'a>, Vec>); - #[derive(Debug, PartialEq)] pub(crate) struct Macro<'a> { pub(crate) ws1: Ws, @@ -234,7 +242,14 @@ fn match_else_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> ))), )); let (i, (_, pws, _, (nws, _, block))) = p(i)?; - Ok((i, (Ws(pws, nws), Target::Name("_"), block))) + Ok(( + i, + When { + ws: Ws(pws, nws), + target: Target::Name("_"), + block, + }, + )) } fn when_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> { @@ -250,7 +265,14 @@ fn when_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> { ))), )); let (i, (_, pws, _, (target, nws, _, block))) = p(i)?; - Ok((i, (Ws(pws, nws), target, block))) + Ok(( + i, + When { + ws: Ws(pws, nws), + target, + block, + }, + )) } fn block_match<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { From b58604030568505fc9beca91211586e57195e8ee Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 02:54:55 -0500 Subject: [PATCH 05/27] Make Cond a proper struct --- askama_derive/src/generator.rs | 8 ++++---- askama_derive/src/heritage.rs | 6 +++--- askama_derive/src/parser/node.rs | 27 +++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 255060aaa..1006e2a43 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -743,8 +743,8 @@ impl<'a> Generator<'a> { let mut flushed = 0; let mut arm_sizes = Vec::new(); let mut has_else = false; - for (i, &(cws, ref cond, ref nodes)) in conds.iter().enumerate() { - self.handle_ws(cws); + for (i, cond) in conds.iter().enumerate() { + self.handle_ws(cond.ws); flushed += self.write_buf_writable(buf)?; if i > 0 { self.locals.pop(); @@ -752,7 +752,7 @@ impl<'a> Generator<'a> { self.locals.push(); let mut arm_size = 0; - if let Some(CondTest { target, expr }) = cond { + if let Some(CondTest { target, expr }) = &cond.test { if i == 0 { buf.write("if "); } else { @@ -787,7 +787,7 @@ impl<'a> Generator<'a> { buf.writeln(" {")?; - arm_size += self.handle(ctx, nodes, buf, AstLevel::Nested)?; + arm_size += self.handle(ctx, &cond.block, buf, AstLevel::Nested)?; arm_sizes.push(arm_size); } self.handle_ws(ws); diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 2624162c0..937ccc1cb 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use crate::config::Config; -use crate::parser::{Loop, Macro, Match, Node, When}; +use crate::parser::{Cond, Loop, Macro, Match, Node, When}; use crate::CompileError; pub(crate) struct Heritage<'a> { @@ -83,8 +83,8 @@ impl Context<'_> { } } Node::Cond(branches, _) => { - for (_, _, nodes) in branches { - nested.push(nodes); + for Cond { block, .. } in branches { + nested.push(block); } } Node::Loop(Loop { diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index 8bede5d81..cd1ea48ff 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -123,7 +123,15 @@ pub(crate) struct Macro<'a> { #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct Ws(pub(crate) Option, pub(crate) Option); -pub(crate) type Cond<'a> = (Ws, Option>, Vec>); +/// A single branch of a conditional statement. +#[derive(Debug, PartialEq)] +pub(crate) struct Cond<'a> { + pub(crate) ws: Ws, + /// The test for this branch, or `None` for the `else` branch. + pub(crate) test: Option>, + /// Body of this conditional branch. + pub(crate) block: Vec>, +} #[derive(Debug, PartialEq)] pub(crate) struct CondTest<'a> { @@ -200,8 +208,15 @@ fn cond_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Cond<'a>> { cut(|i| parse_template(i, s)), ))), )); - let (i, (_, pws, _, (cond, nws, _, block))) = p(i)?; - Ok((i, (Ws(pws, nws), cond, block))) + let (i, (_, pws, _, (test, nws, _, block))) = p(i)?; + Ok(( + i, + Cond { + ws: Ws(pws, nws), + test, + block, + }, + )) } fn block_if<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { @@ -225,7 +240,11 @@ fn block_if<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { )); let (i, (pws1, cond, (nws1, _, (block, elifs, (_, pws2, _, nws2))))) = p(i)?; - let mut res = vec![(Ws(pws1, nws1), Some(cond), block)]; + let mut res = vec![Cond { + ws: Ws(pws1, nws1), + test: Some(cond), + block, + }]; res.extend(elifs); Ok((i, Node::Cond(res, Ws(pws2, nws2)))) } From 27a207544baa2e0e1b9a56adc939a816821acd0e Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 03:08:24 -0500 Subject: [PATCH 06/27] Extract Lit struct from Node --- askama_derive/src/generator.rs | 12 ++++++------ askama_derive/src/parser/mod.rs | 12 +++++++----- askama_derive/src/parser/node.rs | 19 ++++++++++++------- askama_derive/src/parser/tests.rs | 16 +++++----------- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 1006e2a43..ba27b6c79 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -2,7 +2,7 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHan use crate::heritage::{Context, Heritage}; use crate::input::{Print, Source, TemplateInput}; use crate::parser::{ - parse, Call, Cond, CondTest, Expr, Loop, Match, Node, Target, When, Whitespace, Ws, + parse, Call, Cond, CondTest, Expr, Lit, Loop, Match, Node, Target, When, Whitespace, Ws, }; use crate::CompileError; @@ -637,8 +637,8 @@ impl<'a> Generator<'a> { let mut size_hint = 0; for n in nodes { match *n { - Node::Lit(lws, val, rws) => { - self.visit_lit(lws, val, rws); + Node::Lit(ref lit) => { + self.visit_lit(lit); } Node::Comment(ws) => { self.write_comment(ws); @@ -691,9 +691,9 @@ impl<'a> Generator<'a> { self.flush_ws(m.ws1); self.prepare_ws(m.ws2); } - Node::Raw(ws1, lws, val, rws, ws2) => { + Node::Raw(ws1, ref lit, ws2) => { self.handle_ws(ws1); - self.visit_lit(lws, val, rws); + self.visit_lit(lit); self.handle_ws(ws2); } Node::Import(ws, _, _) => { @@ -1285,7 +1285,7 @@ impl<'a> Generator<'a> { Ok(size_hint) } - fn visit_lit(&mut self, lws: &'a str, val: &'a str, rws: &'a str) { + fn visit_lit(&mut self, Lit { lws, val, rws }: &'a Lit<'_>) { assert!(self.next_ws.is_none()); if !lws.is_empty() { match self.skip_ws { diff --git a/askama_derive/src/parser/mod.rs b/askama_derive/src/parser/mod.rs index 29e8191e3..159d33938 100644 --- a/askama_derive/src/parser/mod.rs +++ b/askama_derive/src/parser/mod.rs @@ -13,7 +13,7 @@ use nom::{error_position, AsChar, IResult, InputTakeAtPosition}; pub(crate) use self::expr::Expr; pub(crate) use self::node::{ - Call, Cond, CondTest, Loop, Macro, Match, Node, Target, When, Whitespace, Ws, + Call, Cond, CondTest, Lit, Loop, Macro, Match, Node, Target, When, Whitespace, Ws, }; use crate::config::Syntax; use crate::CompileError; @@ -113,11 +113,13 @@ fn ws<'a, O>( delimited(take_till(not_ws), inner, take_till(not_ws)) } -fn split_ws_parts(s: &str) -> Node<'_> { +fn split_ws_parts(s: &str) -> Lit<'_> { let trimmed_start = s.trim_start_matches(is_ws); let len_start = s.len() - trimmed_start.len(); - let trimmed = trimmed_start.trim_end_matches(is_ws); - Node::Lit(&s[..len_start], trimmed, &trimmed_start[trimmed.len()..]) + let val = trimmed_start.trim_end_matches(is_ws); + let lws = &s[..len_start]; + let rws = &trimmed_start[val.len()..]; + Lit { lws, val, rws } } /// Skips input until `end` was found, but does not consume it. @@ -291,7 +293,7 @@ fn take_content<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { Some(content) => (i, content), None => ("", i), // there is no {block,comment,expr}_start: take everything }; - Ok((i, split_ws_parts(content))) + Ok((i, Node::Lit(split_ws_parts(content)))) } fn tag_block_start<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, &'a str> { diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index cd1ea48ff..ffc1fbbce 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -18,7 +18,7 @@ use crate::config::WhitespaceHandling; #[derive(Debug, PartialEq)] pub(crate) enum Node<'a> { - Lit(&'a str, &'a str, &'a str), + Lit(Lit<'a>), Comment(Ws), Expr(Ws, Expr<'a>), Call(Ws, Call<'a>), @@ -32,7 +32,7 @@ pub(crate) enum Node<'a> { Include(Ws, &'a str), Import(Ws, &'a str, &'a str), Macro(&'a str, Macro<'a>), - Raw(Ws, &'a str, &'a str, &'a str, Ws), + Raw(Ws, Lit<'a>, Ws), Break(Ws), Continue(Ws), } @@ -66,6 +66,14 @@ impl From for Whitespace { } } +/// A literal bit of text to output directly. +#[derive(Debug, PartialEq)] +pub(crate) struct Lit<'a> { + pub(crate) lws: &'a str, + pub(crate) val: &'a str, + pub(crate) rws: &'a str, +} + /// A macro call statement. #[derive(Debug, PartialEq)] pub(crate) struct Call<'a> { @@ -535,13 +543,10 @@ fn block_raw<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { )); let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?; - let (lws, val, rws) = match split_ws_parts(contents) { - Node::Lit(lws, val, rws) => (lws, val, rws), - _ => unreachable!(), - }; + let lit = split_ws_parts(contents); let ws1 = Ws(pws1, nws1); let ws2 = Ws(pws2, nws2); - Ok((i, Node::Raw(ws1, lws, val, rws, ws2))) + Ok((i, Node::Raw(ws1, lit, ws2))) } fn break_statement<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index b00ed7f31..af347bac0 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -1,17 +1,11 @@ use crate::config::Syntax; -use crate::parser::{Expr, Node, Whitespace, Ws}; +use crate::parser::{Expr, Lit, Node, Whitespace, Ws}; fn check_ws_split(s: &str, res: &(&str, &str, &str)) { - match super::split_ws_parts(s) { - Node::Lit(lws, s, rws) => { - assert_eq!(lws, res.0); - assert_eq!(s, res.1); - assert_eq!(rws, res.2); - } - _ => { - panic!("fail"); - } - } + let Lit { lws, val, rws } = super::split_ws_parts(s); + assert_eq!(lws, res.0); + assert_eq!(val, res.1); + assert_eq!(rws, res.2); } #[test] From 0b6bbca3b55346fab98ad5820c0d4e9455364064 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 03:16:43 -0500 Subject: [PATCH 07/27] Push name into Macro struct --- askama_derive/src/generator.rs | 2 +- askama_derive/src/heritage.rs | 6 +++--- askama_derive/src/parser/node.rs | 17 ++++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index ba27b6c79..949ad6989 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -684,7 +684,7 @@ impl<'a> Generator<'a> { ) => { size_hint += self.write_call(ctx, buf, ws, scope, name, args)?; } - Node::Macro(_, ref m) => { + Node::Macro(ref m) => { if level != AstLevel::Top { return Err("macro blocks only allowed at the top level".into()); } diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 937ccc1cb..f1a1bad40 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -64,14 +64,14 @@ impl Context<'_> { extends = Some(config.find_template(extends_path, Some(path))?); } }, - Node::Macro(name, m) if top => { - macros.insert(*name, m); + Node::Macro(m) if top => { + macros.insert(m.name, m); } Node::Import(_, import_path, scope) if top => { let path = config.find_template(import_path, Some(path))?; imports.insert(*scope, path); } - Node::Extends(_) | Node::Macro(_, _) | Node::Import(_, _, _) if !top => { + Node::Extends(_) | Node::Macro(_) | Node::Import(_, _, _) if !top => { return Err( "extends, macro or import blocks not allowed below top level".into(), ); diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index ffc1fbbce..1f0f41219 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -31,7 +31,7 @@ pub(crate) enum Node<'a> { BlockDef(Ws, &'a str, Vec>, Ws), Include(Ws, &'a str), Import(Ws, &'a str, &'a str), - Macro(&'a str, Macro<'a>), + Macro(Macro<'a>), Raw(Ws, Lit<'a>, Ws), Break(Ws), Continue(Ws), @@ -119,6 +119,7 @@ pub(crate) struct Loop<'a> { #[derive(Debug, PartialEq)] pub(crate) struct Macro<'a> { + pub(crate) name: &'a str, pub(crate) ws1: Ws, pub(crate) args: Vec<&'a str>, pub(crate) nodes: Vec>, @@ -511,15 +512,13 @@ fn block_macro<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { Ok(( i, - Node::Macro( + Node::Macro(Macro { name, - Macro { - ws1: Ws(pws1, nws1), - args: params, - nodes: contents, - ws2: Ws(pws2, nws2), - }, - ), + ws1: Ws(pws1, nws1), + args: params, + nodes: contents, + ws2: Ws(pws2, nws2), + }), )) } From ccc6f50af26063ceddedee8c261ba31ae7edd9aa Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 03:25:21 -0500 Subject: [PATCH 08/27] Add test for Macro parse --- askama_derive/src/parser/tests.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index af347bac0..e28cf17e2 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -688,3 +688,19 @@ fn test_parse_call_statement() { )], ); } + +#[test] +fn test_parse_macro_statement() { + use super::Macro; + let syntax = Syntax::default(); + assert_eq!( + super::parse("{% macro foo(bar) -%}{%~ endmacro +%}", &syntax).unwrap(), + vec![Node::Macro(Macro { + name: "foo", + ws1: Ws(None, Some(Whitespace::Suppress)), + args: vec!["bar"], + nodes: vec![], + ws2: Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + },)] + ); +} From 00c4e93081e68a4c4a56b8adf66ba19441f02aff Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 03:27:39 -0500 Subject: [PATCH 09/27] Clarify Macro Ws semantics --- askama_derive/src/generator.rs | 9 ++++----- askama_derive/src/heritage.rs | 4 ++-- askama_derive/src/parser/node.rs | 21 +++++++++++---------- askama_derive/src/parser/tests.rs | 16 +++++++++------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 949ad6989..607547523 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -684,12 +684,11 @@ impl<'a> Generator<'a> { ) => { size_hint += self.write_call(ctx, buf, ws, scope, name, args)?; } - Node::Macro(ref m) => { + Node::Macro(ws, _) => { if level != AstLevel::Top { return Err("macro blocks only allowed at the top level".into()); } - self.flush_ws(m.ws1); - self.prepare_ws(m.ws2); + self.handle_ws(ws); } Node::Raw(ws1, ref lit, ws2) => { self.handle_ws(ws1); @@ -958,7 +957,7 @@ impl<'a> Generator<'a> { self.locals.push(); self.write_buf_writable(buf)?; buf.writeln("{")?; - self.prepare_ws(def.ws1); + self.prepare_ws(def.ws); let mut names = Buffer::new(0); let mut values = Buffer::new(0); @@ -1011,7 +1010,7 @@ impl<'a> Generator<'a> { let mut size_hint = self.handle(own_ctx, &def.nodes, buf, AstLevel::Nested)?; - self.flush_ws(def.ws2); + self.flush_ws(def.ws); size_hint += self.write_buf_writable(buf)?; buf.writeln("}")?; self.locals.pop(); diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index f1a1bad40..75ed39e34 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -64,14 +64,14 @@ impl Context<'_> { extends = Some(config.find_template(extends_path, Some(path))?); } }, - Node::Macro(m) if top => { + Node::Macro(_, m) if top => { macros.insert(m.name, m); } Node::Import(_, import_path, scope) if top => { let path = config.find_template(import_path, Some(path))?; imports.insert(*scope, path); } - Node::Extends(_) | Node::Macro(_) | Node::Import(_, _, _) if !top => { + Node::Extends(_) | Node::Macro(_, _) | Node::Import(_, _, _) if !top => { return Err( "extends, macro or import blocks not allowed below top level".into(), ); diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index 1f0f41219..ed16a0677 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -31,7 +31,7 @@ pub(crate) enum Node<'a> { BlockDef(Ws, &'a str, Vec>, Ws), Include(Ws, &'a str), Import(Ws, &'a str, &'a str), - Macro(Macro<'a>), + Macro(Ws, Macro<'a>), Raw(Ws, Lit<'a>, Ws), Break(Ws), Continue(Ws), @@ -120,10 +120,9 @@ pub(crate) struct Loop<'a> { #[derive(Debug, PartialEq)] pub(crate) struct Macro<'a> { pub(crate) name: &'a str, - pub(crate) ws1: Ws, pub(crate) args: Vec<&'a str>, pub(crate) nodes: Vec>, - pub(crate) ws2: Ws, + pub(crate) ws: Ws, } /// First field is "minus/plus sign was used on the left part of the item". @@ -512,13 +511,15 @@ fn block_macro<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { Ok(( i, - Node::Macro(Macro { - name, - ws1: Ws(pws1, nws1), - args: params, - nodes: contents, - ws2: Ws(pws2, nws2), - }), + Node::Macro( + Ws(pws1, nws2), + Macro { + name, + args: params, + nodes: contents, + ws: Ws(pws2, nws1), + }, + ), )) } diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index e28cf17e2..aa37306b5 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -695,12 +695,14 @@ fn test_parse_macro_statement() { let syntax = Syntax::default(); assert_eq!( super::parse("{% macro foo(bar) -%}{%~ endmacro +%}", &syntax).unwrap(), - vec![Node::Macro(Macro { - name: "foo", - ws1: Ws(None, Some(Whitespace::Suppress)), - args: vec!["bar"], - nodes: vec![], - ws2: Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), - },)] + vec![Node::Macro( + Ws(None, Some(Whitespace::Preserve)), + Macro { + name: "foo", + args: vec!["bar"], + nodes: vec![], + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + }, + )] ); } From 92bf3b87ab00720e0bcff0e467a228b853c48004 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 04:01:26 -0500 Subject: [PATCH 10/27] Add test for parsing raw blocks --- askama_derive/src/parser/tests.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index aa37306b5..df845b8de 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -706,3 +706,24 @@ fn test_parse_macro_statement() { )] ); } + +#[test] +fn test_parse_raw_block() { + let syntax = Syntax::default(); + assert_eq!( + super::parse( + "{% raw -%}{% if condition %}{{ result }}{% endif %}{%~ endraw +%}", + &syntax + ) + .unwrap(), + vec![Node::Raw( + Ws(None, Some(Whitespace::Suppress)), + Lit { + lws: "", + val: "{% if condition %}{{ result }}{% endif %}", + rws: "", + }, + Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + )] + ); +} From cfcf8c8752443398b304d0c067bbbe358a3619d8 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 03:36:12 -0500 Subject: [PATCH 11/27] Extract Raw struct from Node and clarify inner/outer Ws --- askama_derive/src/generator.rs | 16 +++++++++++----- askama_derive/src/parser/mod.rs | 2 +- askama_derive/src/parser/node.rs | 15 +++++++++++---- askama_derive/src/parser/tests.rs | 17 ++++++++++------- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 607547523..9b5b8638f 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -2,7 +2,7 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHan use crate::heritage::{Context, Heritage}; use crate::input::{Print, Source, TemplateInput}; use crate::parser::{ - parse, Call, Cond, CondTest, Expr, Lit, Loop, Match, Node, Target, When, Whitespace, Ws, + parse, Call, Cond, CondTest, Expr, Lit, Loop, Match, Node, Raw, Target, When, Whitespace, Ws, }; use crate::CompileError; @@ -690,10 +690,10 @@ impl<'a> Generator<'a> { } self.handle_ws(ws); } - Node::Raw(ws1, ref lit, ws2) => { - self.handle_ws(ws1); - self.visit_lit(lit); - self.handle_ws(ws2); + Node::Raw(ws, ref raw) => { + self.flush_ws(ws); + self.visit_raw(raw); + self.prepare_ws(ws); } Node::Import(ws, _, _) => { if level != AstLevel::Top { @@ -1284,6 +1284,12 @@ impl<'a> Generator<'a> { Ok(size_hint) } + fn visit_raw(&mut self, Raw { lit, ws }: &'a Raw<'_>) { + self.prepare_ws(*ws); + self.visit_lit(lit); + self.flush_ws(*ws); + } + fn visit_lit(&mut self, Lit { lws, val, rws }: &'a Lit<'_>) { assert!(self.next_ws.is_none()); if !lws.is_empty() { diff --git a/askama_derive/src/parser/mod.rs b/askama_derive/src/parser/mod.rs index 159d33938..daa9848c3 100644 --- a/askama_derive/src/parser/mod.rs +++ b/askama_derive/src/parser/mod.rs @@ -13,7 +13,7 @@ use nom::{error_position, AsChar, IResult, InputTakeAtPosition}; pub(crate) use self::expr::Expr; pub(crate) use self::node::{ - Call, Cond, CondTest, Lit, Loop, Macro, Match, Node, Target, When, Whitespace, Ws, + Call, Cond, CondTest, Lit, Loop, Macro, Match, Node, Raw, Target, When, Whitespace, Ws, }; use crate::config::Syntax; use crate::CompileError; diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index ed16a0677..b14662deb 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -32,7 +32,7 @@ pub(crate) enum Node<'a> { Include(Ws, &'a str), Import(Ws, &'a str, &'a str), Macro(Ws, Macro<'a>), - Raw(Ws, Lit<'a>, Ws), + Raw(Ws, Raw<'a>), Break(Ws), Continue(Ws), } @@ -74,6 +74,13 @@ pub(crate) struct Lit<'a> { pub(crate) rws: &'a str, } +/// A raw block to output directly. +#[derive(Debug, PartialEq)] +pub(crate) struct Raw<'a> { + pub(crate) lit: Lit<'a>, + pub(crate) ws: Ws, +} + /// A macro call statement. #[derive(Debug, PartialEq)] pub(crate) struct Call<'a> { @@ -544,9 +551,9 @@ fn block_raw<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?; let lit = split_ws_parts(contents); - let ws1 = Ws(pws1, nws1); - let ws2 = Ws(pws2, nws2); - Ok((i, Node::Raw(ws1, lit, ws2))) + let outer = Ws(pws1, nws2); + let ws = Ws(pws2, nws1); + Ok((i, Node::Raw(outer, Raw { lit, ws }))) } fn break_statement<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index df845b8de..69bdf67fb 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -709,6 +709,7 @@ fn test_parse_macro_statement() { #[test] fn test_parse_raw_block() { + use super::Raw; let syntax = Syntax::default(); assert_eq!( super::parse( @@ -717,13 +718,15 @@ fn test_parse_raw_block() { ) .unwrap(), vec![Node::Raw( - Ws(None, Some(Whitespace::Suppress)), - Lit { - lws: "", - val: "{% if condition %}{{ result }}{% endif %}", - rws: "", - }, - Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + Ws(None, Some(Whitespace::Preserve)), + Raw { + lit: Lit { + lws: "", + val: "{% if condition %}{{ result }}{% endif %}", + rws: "", + }, + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + } )] ); } From eb71bdc337b2da11fa689fdb53dda9e68def0f6b Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 04:06:09 -0500 Subject: [PATCH 12/27] Add test for BlockDef parse --- askama_derive/src/parser/tests.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 69bdf67fb..1d82e2e07 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -730,3 +730,17 @@ fn test_parse_raw_block() { )] ); } + +#[test] +fn test_parse_block_def() { + let syntax = Syntax::default(); + assert_eq!( + super::parse("{% block foo -%}{%~ endblock +%}", &syntax).unwrap(), + vec![Node::BlockDef( + Ws(None, Some(Whitespace::Suppress)), + "foo", + vec![], + Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + )], + ); +} From 21bf9a5727bad0dee944c06fbec99e1a9f10f646 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 03:54:13 -0500 Subject: [PATCH 13/27] Extract BlockDef struct from Node and clarify inner/outer Ws --- askama_derive/src/generator.rs | 19 ++++++++----------- askama_derive/src/heritage.rs | 19 +++++++------------ askama_derive/src/parser/mod.rs | 3 ++- askama_derive/src/parser/node.rs | 21 ++++++++++++++++++--- askama_derive/src/parser/tests.rs | 11 +++++++---- 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 9b5b8638f..fb6c9fea5 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -2,7 +2,8 @@ use crate::config::{get_template_source, read_config_file, Config, WhitespaceHan use crate::heritage::{Context, Heritage}; use crate::input::{Print, Source, TemplateInput}; use crate::parser::{ - parse, Call, Cond, CondTest, Expr, Lit, Loop, Match, Node, Raw, Target, When, Whitespace, Ws, + parse, BlockDef, Call, Cond, CondTest, Expr, Lit, Loop, Match, Node, Raw, Target, When, + Whitespace, Ws, }; use crate::CompileError; @@ -668,8 +669,8 @@ impl<'a> Generator<'a> { Node::Loop(ref loop_block) => { size_hint += self.write_loop(ctx, buf, loop_block)?; } - Node::BlockDef(ws1, name, _, ws2) => { - size_hint += self.write_block(buf, Some(name), Ws(ws1.0, ws2.1))?; + Node::BlockDef(ws, BlockDef { name, .. }) => { + size_hint += self.write_block(buf, Some(name), ws)?; } Node::Include(ws, path) => { size_hint += self.handle_include(ctx, buf, ws, path)?; @@ -1175,16 +1176,12 @@ impl<'a> Generator<'a> { })?; // Get the nodes and whitespace suppression data from the block definition - let (ws1, nodes, ws2) = if let Node::BlockDef(ws1, _, nodes, ws2) = def { - (ws1, nodes, ws2) - } else { - unreachable!() - }; + let BlockDef { block, ws, .. } = def; // Handle inner whitespace suppression spec and process block nodes - self.prepare_ws(*ws1); + self.prepare_ws(*ws); self.locals.push(); - let size_hint = self.handle(ctx, nodes, buf, AstLevel::Block)?; + let size_hint = self.handle(ctx, block, buf, AstLevel::Block)?; if !self.locals.is_current_empty() { // Need to flush the buffer before popping the variable stack @@ -1192,7 +1189,7 @@ impl<'a> Generator<'a> { } self.locals.pop(); - self.flush_ws(*ws2); + self.flush_ws(*ws); // Restore original block context and set whitespace suppression for // succeeding whitespace according to the outer WS spec diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 75ed39e34..4fa271d0a 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use crate::config::Config; -use crate::parser::{Cond, Loop, Macro, Match, Node, When}; +use crate::parser::{BlockDef, Cond, Loop, Macro, Match, Node, When}; use crate::CompileError; pub(crate) struct Heritage<'a> { @@ -32,12 +32,12 @@ impl Heritage<'_> { } } -type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a Node<'a>)>>; +type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>>; pub(crate) struct Context<'a> { pub(crate) nodes: &'a [Node<'a>], pub(crate) extends: Option, - pub(crate) blocks: HashMap<&'a str, &'a Node<'a>>, + pub(crate) blocks: HashMap<&'a str, &'a BlockDef<'a>>, pub(crate) macros: HashMap<&'a str, &'a Macro<'a>>, pub(crate) imports: HashMap<&'a str, PathBuf>, } @@ -76,11 +76,9 @@ impl Context<'_> { "extends, macro or import blocks not allowed below top level".into(), ); } - def @ Node::BlockDef(_, _, _, _) => { + Node::BlockDef(_, def @ BlockDef { block, .. }) => { blocks.push(def); - if let Node::BlockDef(_, _, nodes, _) = def { - nested.push(nodes); - } + nested.push(block); } Node::Cond(branches, _) => { for Cond { block, .. } in branches { @@ -107,11 +105,8 @@ impl Context<'_> { let blocks: HashMap<_, _> = blocks .iter() .map(|def| { - if let Node::BlockDef(_, name, _, _) = def { - (*name, *def) - } else { - unreachable!() - } + let BlockDef { name, .. } = def; + (*name, *def) }) .collect(); diff --git a/askama_derive/src/parser/mod.rs b/askama_derive/src/parser/mod.rs index daa9848c3..32c85398d 100644 --- a/askama_derive/src/parser/mod.rs +++ b/askama_derive/src/parser/mod.rs @@ -13,7 +13,8 @@ use nom::{error_position, AsChar, IResult, InputTakeAtPosition}; pub(crate) use self::expr::Expr; pub(crate) use self::node::{ - Call, Cond, CondTest, Lit, Loop, Macro, Match, Node, Raw, Target, When, Whitespace, Ws, + BlockDef, Call, Cond, CondTest, Lit, Loop, Macro, Match, Node, Raw, Target, When, Whitespace, + Ws, }; use crate::config::Syntax; use crate::CompileError; diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index b14662deb..a03a26498 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -28,7 +28,7 @@ pub(crate) enum Node<'a> { Match(Ws, Match<'a>), Loop(Loop<'a>), Extends(&'a str), - BlockDef(Ws, &'a str, Vec>, Ws), + BlockDef(Ws, BlockDef<'a>), Include(Ws, &'a str), Import(Ws, &'a str, &'a str), Macro(Ws, Macro<'a>), @@ -132,6 +132,14 @@ pub(crate) struct Macro<'a> { pub(crate) ws: Ws, } +/// A block statement, either a definition or a reference. +#[derive(Debug, PartialEq)] +pub(crate) struct BlockDef<'a> { + pub(crate) name: &'a str, + pub(crate) block: Vec>, + pub(crate) ws: Ws, +} + /// First field is "minus/plus sign was used on the left part of the item". /// /// Second field is "minus/plus sign was used on the right part of the item". @@ -456,11 +464,18 @@ fn block_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { cut(tuple((opt(ws(keyword(name))), opt(expr_handle_ws)))), ))), ))); - let (i, (contents, (_, pws2, _, (_, nws2)))) = end(i)?; + let (i, (block, (_, pws2, _, (_, nws2)))) = end(i)?; Ok(( i, - Node::BlockDef(Ws(pws1, nws1), name, contents, Ws(pws2, nws2)), + Node::BlockDef( + Ws(pws1, nws2), + BlockDef { + name, + block, + ws: Ws(pws2, nws1), + }, + ), )) } diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 1d82e2e07..06e4f0fd7 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -733,14 +733,17 @@ fn test_parse_raw_block() { #[test] fn test_parse_block_def() { + use super::BlockDef; let syntax = Syntax::default(); assert_eq!( super::parse("{% block foo -%}{%~ endblock +%}", &syntax).unwrap(), vec![Node::BlockDef( - Ws(None, Some(Whitespace::Suppress)), - "foo", - vec![], - Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + Ws(None, Some(Whitespace::Preserve)), + BlockDef { + name: "foo", + block: vec![], + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + } )], ); } From 8dd962afc210b92f34d4e1472bfba3c272b1bac0 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Sat, 4 Mar 2023 02:32:19 -0500 Subject: [PATCH 14/27] Add test for parsing Node::Loop --- askama_derive/src/parser/tests.rs | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 06e4f0fd7..203f35112 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -651,6 +651,38 @@ fn test_parse_tuple() { ); } +#[test] +fn test_parse_loop() { + use super::{Expr, Loop, Target}; + let syntax = Syntax::default(); + assert_eq!( + super::parse("{% for user in users +%}{%~ else -%}{%+ endfor %}", &syntax).unwrap(), + vec![Node::Loop(Loop { + ws1: Ws(None, Some(Whitespace::Preserve)), + var: Target::Name("user"), + iter: Expr::Var("users"), + cond: None, + body: vec![], + ws2: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + else_block: vec![], + ws3: Ws(Some(Whitespace::Preserve), None), + })] + ); + assert_eq!( + super::parse("{% for user in users +%}{%~ endfor -%}", &syntax).unwrap(), + vec![Node::Loop(Loop { + ws1: Ws(None, Some(Whitespace::Preserve)), + var: Target::Name("user"), + iter: Expr::Var("users"), + cond: None, + body: vec![], + ws2: Ws(Some(Whitespace::Minimize), None), + else_block: vec![], + ws3: Ws(None, Some(Whitespace::Suppress)), + })] + ); +} + #[test] fn test_missing_space_after_kw() { let syntax = Syntax::default(); From 3ea5da86355f60a42571cc76289ea24dd56cf90d Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 04:28:58 -0500 Subject: [PATCH 15/27] Clarify Loop outer/body/else Ws --- askama_derive/src/generator.rs | 11 +++++--- askama_derive/src/heritage.rs | 9 ++++--- askama_derive/src/parser/node.rs | 29 ++++++++++---------- askama_derive/src/parser/tests.rs | 44 +++++++++++++++++-------------- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index fb6c9fea5..e4cd81747 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -666,8 +666,10 @@ impl<'a> Generator<'a> { ) => { size_hint += self.write_match(ctx, buf, ws1, expr, arms, ws)?; } - Node::Loop(ref loop_block) => { + Node::Loop(ws, ref loop_block) => { + self.flush_ws(ws); size_hint += self.write_loop(ctx, buf, loop_block)?; + self.prepare_ws(ws); } Node::BlockDef(ws, BlockDef { name, .. }) => { size_hint += self.write_block(buf, Some(name), ws)?; @@ -855,7 +857,6 @@ impl<'a> Generator<'a> { buf: &mut Buffer, loop_block: &'a Loop<'_>, ) -> Result { - self.handle_ws(loop_block.ws1); self.locals.push(); let expr_code = self.visit_expr_root(&loop_block.iter)?; @@ -899,16 +900,18 @@ impl<'a> Generator<'a> { buf.writeln(", _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {")?; buf.writeln("_did_loop = true;")?; + self.prepare_ws(loop_block.body_ws); let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?; - self.handle_ws(loop_block.ws2); + self.flush_ws(loop_block.body_ws); size_hint1 += self.write_buf_writable(buf)?; self.locals.pop(); buf.writeln("}")?; buf.writeln("if !_did_loop {")?; self.locals.push(); + self.prepare_ws(loop_block.else_ws); let mut size_hint2 = self.handle(ctx, &loop_block.else_block, buf, AstLevel::Nested)?; - self.handle_ws(loop_block.ws3); + self.flush_ws(loop_block.else_ws); size_hint2 += self.write_buf_writable(buf)?; self.locals.pop(); buf.writeln("}")?; diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 4fa271d0a..17318ea83 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -85,9 +85,12 @@ impl Context<'_> { nested.push(block); } } - Node::Loop(Loop { - body, else_block, .. - }) => { + Node::Loop( + _, + Loop { + body, else_block, .. + }, + ) => { nested.push(body); nested.push(else_block); } diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index a03a26498..1d0096e37 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -26,7 +26,7 @@ pub(crate) enum Node<'a> { Let(Ws, Target<'a>, Expr<'a>), Cond(Vec>, Ws), Match(Ws, Match<'a>), - Loop(Loop<'a>), + Loop(Ws, Loop<'a>), Extends(&'a str), BlockDef(Ws, BlockDef<'a>), Include(Ws, &'a str), @@ -114,14 +114,13 @@ pub(crate) struct When<'a> { #[derive(Debug, PartialEq)] pub(crate) struct Loop<'a> { - pub(crate) ws1: Ws, pub(crate) var: Target<'a>, pub(crate) iter: Expr<'a>, pub(crate) cond: Option>, pub(crate) body: Vec>, - pub(crate) ws2: Ws, + pub(crate) body_ws: Ws, pub(crate) else_block: Vec>, - pub(crate) ws3: Ws, + pub(crate) else_ws: Ws, } #[derive(Debug, PartialEq)] @@ -427,16 +426,18 @@ fn block_for<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { let (nws3, else_block, pws3) = else_block.unwrap_or_default(); Ok(( i, - Node::Loop(Loop { - ws1: Ws(pws1, nws1), - var, - iter, - cond, - body, - ws2: Ws(pws2, nws3), - else_block, - ws3: Ws(pws3, nws2), - }), + Node::Loop( + Ws(pws1, nws2), + Loop { + var, + iter, + cond, + body, + body_ws: Ws(pws2, nws1), + else_block, + else_ws: Ws(pws3, nws3), + }, + ), )) } diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 203f35112..7dc1ead75 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -657,29 +657,33 @@ fn test_parse_loop() { let syntax = Syntax::default(); assert_eq!( super::parse("{% for user in users +%}{%~ else -%}{%+ endfor %}", &syntax).unwrap(), - vec![Node::Loop(Loop { - ws1: Ws(None, Some(Whitespace::Preserve)), - var: Target::Name("user"), - iter: Expr::Var("users"), - cond: None, - body: vec![], - ws2: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), - else_block: vec![], - ws3: Ws(Some(Whitespace::Preserve), None), - })] + vec![Node::Loop( + Ws(None, None), + Loop { + var: Target::Name("user"), + iter: Expr::Var("users"), + cond: None, + body: vec![], + body_ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + else_block: vec![], + else_ws: Ws(Some(Whitespace::Preserve), Some(Whitespace::Suppress)), + }, + )] ); assert_eq!( super::parse("{% for user in users +%}{%~ endfor -%}", &syntax).unwrap(), - vec![Node::Loop(Loop { - ws1: Ws(None, Some(Whitespace::Preserve)), - var: Target::Name("user"), - iter: Expr::Var("users"), - cond: None, - body: vec![], - ws2: Ws(Some(Whitespace::Minimize), None), - else_block: vec![], - ws3: Ws(None, Some(Whitespace::Suppress)), - })] + vec![Node::Loop( + Ws(None, Some(Whitespace::Suppress)), + Loop { + var: Target::Name("user"), + iter: Expr::Var("users"), + cond: None, + body: vec![], + body_ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + else_block: vec![], + else_ws: Ws(None, None), + }, + )] ); } From d551120cbbe6a0c09547dde1e94e886d1be43d47 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Sat, 4 Mar 2023 04:13:41 -0500 Subject: [PATCH 16/27] Add test for parsing Node::Match Ws --- askama_derive/src/parser/tests.rs | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 7dc1ead75..bb646ed81 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -1,5 +1,5 @@ use crate::config::Syntax; -use crate::parser::{Expr, Lit, Node, Whitespace, Ws}; +use crate::parser::{Expr, Lit, Node, Target, Whitespace, Ws}; fn check_ws_split(s: &str, res: &(&str, &str, &str)) { let Lit { lws, val, rws } = super::split_ws_parts(s); @@ -532,6 +532,38 @@ fn test_parse_comments() { ); } +#[test] +fn test_parse_match() { + use super::{Match, When}; + let syntax = Syntax::default(); + assert_eq!( + super::parse( + "{%+ match foo %}{% when Foo ~%}{%- else +%}{%~ endmatch %}", + &syntax + ) + .unwrap(), + vec![Node::Match( + Ws(Some(Whitespace::Preserve), None), + Match { + expr: Expr::Var("foo"), + arms: vec![ + When { + ws: Ws(None, Some(Whitespace::Minimize)), + target: Target::Path(vec!["Foo"]), + block: vec![], + }, + When { + ws: Ws(Some(Whitespace::Suppress), Some(Whitespace::Preserve)), + target: Target::Name("_"), + block: vec![], + } + ], + ws: Ws(Some(Whitespace::Minimize), None), + } + )], + ); +} + #[test] fn test_parse_tuple() { use super::Expr::*; From 4140503e70a04bffe5343c64b6a64e3d82b3e9d2 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 04:47:33 -0500 Subject: [PATCH 17/27] Clarify Match arms/outer Ws semantics --- askama_derive/src/generator.rs | 20 ++++++-------------- askama_derive/src/parser/node.rs | 19 +++++++++++++++---- askama_derive/src/parser/tests.rs | 5 ++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index e4cd81747..9d00507cd 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -656,15 +656,10 @@ impl<'a> Generator<'a> { Node::Cond(ref conds, ws) => { size_hint += self.write_cond(ctx, buf, conds, ws)?; } - Node::Match( - ws1, - Match { - ref expr, - ref arms, - ws, - }, - ) => { - size_hint += self.write_match(ctx, buf, ws1, expr, arms, ws)?; + Node::Match(ws, Match { ref expr, ref arms }) => { + self.flush_ws(ws); + size_hint += self.write_match(ctx, buf, expr, arms)?; + self.prepare_ws(ws); } Node::Loop(ws, ref loop_block) => { self.flush_ws(ws); @@ -809,12 +804,9 @@ impl<'a> Generator<'a> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - ws1: Ws, expr: &Expr<'_>, arms: &'a [When<'_>], - ws2: Ws, ) -> Result { - self.flush_ws(ws1); let flushed = self.write_buf_writable(buf)?; let mut arm_sizes = Vec::new(); @@ -824,7 +816,6 @@ impl<'a> Generator<'a> { let mut arm_size = 0; for (i, arm) in arms.iter().enumerate() { let When { ws, target, block } = arm; - self.handle_ws(*ws); if i > 0 { arm_sizes.push(arm_size + self.write_buf_writable(buf)?); @@ -837,10 +828,11 @@ impl<'a> Generator<'a> { self.visit_target(buf, true, true, target); buf.writeln(" => {")?; + self.prepare_ws(*ws); arm_size = self.handle(ctx, block, buf, AstLevel::Nested)?; + self.flush_ws(*ws); } - self.handle_ws(ws2); arm_sizes.push(arm_size + self.write_buf_writable(buf)?); buf.writeln("}")?; self.locals.pop(); diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index 1d0096e37..59a0e5892 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -99,7 +99,6 @@ pub(crate) struct Match<'a> { pub(crate) expr: Expr<'a>, /// Each of the match arms, with a pattern and a body. pub(crate) arms: Vec>, - pub(crate) ws: Ws, } /// A single arm of a match statement. @@ -339,15 +338,27 @@ fn block_match<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { ))), ))), )); - let (i, (pws1, _, (expr, nws1, _, (_, arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?; - let ws = Ws(pws2, nws2); + let (i, (pws1, _, (expr, _, _, (_, arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?; let mut arms = arms; if let Some(arm) = else_arm { arms.push(arm); } - Ok((i, Node::Match(Ws(pws1, nws1), Match { expr, arms, ws }))) + let outer = Ws(pws1, nws2); + + let mut cursor = pws2; + let mut idx = arms.len() - 1; + loop { + std::mem::swap(&mut cursor, &mut arms[idx].ws.0); + + if idx == 0 { + break; + } + idx -= 1; + } + + Ok((i, Node::Match(outer, Match { expr, arms }))) } fn block_let(i: &str) -> IResult<&str, Node<'_>> { diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index bb646ed81..87c4e0fe9 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -548,17 +548,16 @@ fn test_parse_match() { expr: Expr::Var("foo"), arms: vec![ When { - ws: Ws(None, Some(Whitespace::Minimize)), + ws: Ws(Some(Whitespace::Suppress), Some(Whitespace::Minimize)), target: Target::Path(vec!["Foo"]), block: vec![], }, When { - ws: Ws(Some(Whitespace::Suppress), Some(Whitespace::Preserve)), + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), target: Target::Name("_"), block: vec![], } ], - ws: Ws(Some(Whitespace::Minimize), None), } )], ); From 15cbcd854a990207eb091b2393a8e53a774c96bf Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:02:07 -0500 Subject: [PATCH 18/27] Add test for parsing Node::Cond --- askama_derive/src/parser/tests.rs | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 87c4e0fe9..091c2c54d 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -814,3 +814,70 @@ fn test_parse_block_def() { )], ); } + +#[test] +fn test_parse_cond() { + use super::{Cond, CondTest}; + let syntax = Syntax::default(); + assert_eq!( + super::parse("{% if condition -%}{%~ endif +%}", &syntax).unwrap(), + vec![Node::Cond( + vec![Cond { + test: Some(CondTest { + expr: Expr::Var("condition"), + target: None, + }), + block: vec![], + ws: Ws(None, Some(Whitespace::Suppress)), + },], + Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + )], + ); + assert_eq!( + super::parse("{% if let Some(val) = condition -%}{%~ endif +%}", &syntax).unwrap(), + vec![Node::Cond( + vec![Cond { + test: Some(CondTest { + expr: Expr::Var("condition"), + target: Some(Target::Tuple(vec!["Some"], vec![Target::Name("val")],)), + }), + block: vec![], + ws: Ws(None, Some(Whitespace::Suppress)), + },], + Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + )], + ); + assert_eq!( + super::parse( + "{% if condition -%}{%+ else if other -%}{%~ else %}{%~ endif +%}", + &syntax + ) + .unwrap(), + vec![Node::Cond( + vec![ + Cond { + test: Some(CondTest { + expr: Expr::Var("condition"), + target: None, + }), + block: vec![], + ws: Ws(None, Some(Whitespace::Suppress)), + }, + Cond { + test: Some(CondTest { + expr: Expr::Var("other"), + target: None, + }), + block: vec![], + ws: Ws(Some(Whitespace::Preserve), Some(Whitespace::Suppress)), + }, + Cond { + test: None, + block: vec![], + ws: Ws(Some(Whitespace::Minimize), None), + }, + ], + Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + )], + ); +} From 265702f90c9f7ce8e3c8a778ff880412b3ee2558 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:11:19 -0500 Subject: [PATCH 19/27] Clarify Cond branch/outer Ws semantics --- askama_derive/src/generator.rs | 11 ++++++----- askama_derive/src/heritage.rs | 2 +- askama_derive/src/parser/node.rs | 18 ++++++++++++++++-- askama_derive/src/parser/tests.rs | 14 +++++++------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 9d00507cd..e033b3028 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -653,8 +653,10 @@ impl<'a> Generator<'a> { Node::Let(ws, ref var, ref val) => { self.write_let(buf, ws, var, val)?; } - Node::Cond(ref conds, ws) => { - size_hint += self.write_cond(ctx, buf, conds, ws)?; + Node::Cond(ws, ref conds) => { + self.flush_ws(ws); + size_hint += self.write_cond(ctx, buf, conds)?; + self.prepare_ws(ws); } Node::Match(ws, Match { ref expr, ref arms }) => { self.flush_ws(ws); @@ -735,13 +737,11 @@ impl<'a> Generator<'a> { ctx: &'a Context<'_>, buf: &mut Buffer, conds: &'a [Cond<'_>], - ws: Ws, ) -> Result { let mut flushed = 0; let mut arm_sizes = Vec::new(); let mut has_else = false; for (i, cond) in conds.iter().enumerate() { - self.handle_ws(cond.ws); flushed += self.write_buf_writable(buf)?; if i > 0 { self.locals.pop(); @@ -784,10 +784,11 @@ impl<'a> Generator<'a> { buf.writeln(" {")?; + self.prepare_ws(cond.ws); arm_size += self.handle(ctx, &cond.block, buf, AstLevel::Nested)?; arm_sizes.push(arm_size); + self.flush_ws(cond.ws); } - self.handle_ws(ws); flushed += self.write_buf_writable(buf)?; buf.writeln("}")?; diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 17318ea83..eee476900 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -80,7 +80,7 @@ impl Context<'_> { blocks.push(def); nested.push(block); } - Node::Cond(branches, _) => { + Node::Cond(_, branches) => { for Cond { block, .. } in branches { nested.push(block); } diff --git a/askama_derive/src/parser/node.rs b/askama_derive/src/parser/node.rs index 59a0e5892..4b201444b 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -24,7 +24,7 @@ pub(crate) enum Node<'a> { Call(Ws, Call<'a>), LetDecl(Ws, Target<'a>), Let(Ws, Target<'a>, Expr<'a>), - Cond(Vec>, Ws), + Cond(Ws, Vec>), Match(Ws, Match<'a>), Loop(Ws, Loop<'a>), Extends(&'a str), @@ -267,7 +267,21 @@ fn block_if<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { block, }]; res.extend(elifs); - Ok((i, Node::Cond(res, Ws(pws2, nws2)))) + + let outer = Ws(pws1, nws2); + + let mut cursor = pws2; + let mut idx = res.len() - 1; + loop { + std::mem::swap(&mut cursor, &mut res[idx].ws.0); + + if idx == 0 { + break; + } + idx -= 1; + } + + Ok((i, Node::Cond(outer, res))) } fn match_else_block<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, When<'a>> { diff --git a/askama_derive/src/parser/tests.rs b/askama_derive/src/parser/tests.rs index 091c2c54d..d3d23e563 100644 --- a/askama_derive/src/parser/tests.rs +++ b/askama_derive/src/parser/tests.rs @@ -822,29 +822,29 @@ fn test_parse_cond() { assert_eq!( super::parse("{% if condition -%}{%~ endif +%}", &syntax).unwrap(), vec![Node::Cond( + Ws(None, Some(Whitespace::Preserve)), vec![Cond { test: Some(CondTest { expr: Expr::Var("condition"), target: None, }), block: vec![], - ws: Ws(None, Some(Whitespace::Suppress)), + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), },], - Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), )], ); assert_eq!( super::parse("{% if let Some(val) = condition -%}{%~ endif +%}", &syntax).unwrap(), vec![Node::Cond( + Ws(None, Some(Whitespace::Preserve)), vec![Cond { test: Some(CondTest { expr: Expr::Var("condition"), target: Some(Target::Tuple(vec!["Some"], vec![Target::Name("val")],)), }), block: vec![], - ws: Ws(None, Some(Whitespace::Suppress)), + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), },], - Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), )], ); assert_eq!( @@ -854,6 +854,7 @@ fn test_parse_cond() { ) .unwrap(), vec![Node::Cond( + Ws(None, Some(Whitespace::Preserve)), vec![ Cond { test: Some(CondTest { @@ -861,7 +862,7 @@ fn test_parse_cond() { target: None, }), block: vec![], - ws: Ws(None, Some(Whitespace::Suppress)), + ws: Ws(Some(Whitespace::Preserve), Some(Whitespace::Suppress)), }, Cond { test: Some(CondTest { @@ -869,7 +870,7 @@ fn test_parse_cond() { target: None, }), block: vec![], - ws: Ws(Some(Whitespace::Preserve), Some(Whitespace::Suppress)), + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), }, Cond { test: None, @@ -877,7 +878,6 @@ fn test_parse_cond() { ws: Ws(Some(Whitespace::Minimize), None), }, ], - Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), )], ); } From 9469675d6362c6cdd0304e32ec64937d8a5ff84c Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:14:38 -0500 Subject: [PATCH 20/27] Inline write_comment in Generator::handle --- askama_derive/src/generator.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index e033b3028..dd280aa6b 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -642,7 +642,8 @@ impl<'a> Generator<'a> { self.visit_lit(lit); } Node::Comment(ws) => { - self.write_comment(ws); + self.flush_ws(ws); + self.prepare_ws(ws); } Node::Expr(ws, ref val) => { self.write_expr(ws, val); @@ -1313,10 +1314,6 @@ impl<'a> Generator<'a> { } } - fn write_comment(&mut self, ws: Ws) { - self.handle_ws(ws); - } - /* Visitor methods for expression types */ fn visit_expr_root(&mut self, expr: &Expr<'_>) -> Result { From 2657abd9b727c8368d4e1763288e895e41baf4b5 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:16:57 -0500 Subject: [PATCH 21/27] Handle Expr Ws in Generator::handle --- askama_derive/src/generator.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index dd280aa6b..12af49598 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -646,7 +646,9 @@ impl<'a> Generator<'a> { self.prepare_ws(ws); } Node::Expr(ws, ref val) => { - self.write_expr(ws, val); + self.flush_ws(ws); + self.write_expr(val); + self.prepare_ws(ws); } Node::LetDecl(ws, ref var) => { self.write_let_decl(buf, ws, var)?; @@ -1195,8 +1197,7 @@ impl<'a> Generator<'a> { Ok(size_hint) } - fn write_expr(&mut self, ws: Ws, s: &'a Expr<'a>) { - self.handle_ws(ws); + fn write_expr(&mut self, s: &'a Expr<'a>) { self.buf_writable.push(Writable::Expr(s)); } From ad83909000e2442dbc7712276a0276dd3ecfc66b Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:19:06 -0500 Subject: [PATCH 22/27] Handle LetDecl Ws in Generator::handle --- askama_derive/src/generator.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 12af49598..a615a4f1a 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -651,7 +651,9 @@ impl<'a> Generator<'a> { self.prepare_ws(ws); } Node::LetDecl(ws, ref var) => { - self.write_let_decl(buf, ws, var)?; + self.flush_ws(ws); + self.write_let_decl(buf, var)?; + self.prepare_ws(ws); } Node::Let(ws, ref var, ref val) => { self.write_let(buf, ws, var, val)?; @@ -1060,10 +1062,8 @@ impl<'a> Generator<'a> { fn write_let_decl( &mut self, buf: &mut Buffer, - ws: Ws, var: &'a Target<'_>, ) -> Result<(), CompileError> { - self.handle_ws(ws); self.write_buf_writable(buf)?; buf.write("let "); self.visit_target(buf, false, true, var); From 0b02a31e51f90a33b3d9dae92cdc1e1fe2085bae Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:20:26 -0500 Subject: [PATCH 23/27] Handle Let Ws in Generator::handle --- askama_derive/src/generator.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index a615a4f1a..9cb2aed3d 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -656,7 +656,9 @@ impl<'a> Generator<'a> { self.prepare_ws(ws); } Node::Let(ws, ref var, ref val) => { - self.write_let(buf, ws, var, val)?; + self.flush_ws(ws); + self.write_let(buf, var, val)?; + self.prepare_ws(ws); } Node::Cond(ws, ref conds) => { self.flush_ws(ws); @@ -1108,11 +1110,9 @@ impl<'a> Generator<'a> { fn write_let( &mut self, buf: &mut Buffer, - ws: Ws, var: &'a Target<'_>, val: &Expr<'_>, ) -> Result<(), CompileError> { - self.handle_ws(ws); let mut expr_buf = Buffer::new(0); self.visit_expr(&mut expr_buf, val)?; From 64fbc47abdb629b49db343473caf301882127e3e Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:29:22 -0500 Subject: [PATCH 24/27] Move Node::Include Ws handling to caller --- askama_derive/src/generator.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 9cb2aed3d..4ffbb543f 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -676,10 +676,14 @@ impl<'a> Generator<'a> { self.prepare_ws(ws); } Node::BlockDef(ws, BlockDef { name, .. }) => { - size_hint += self.write_block(buf, Some(name), ws)?; + self.flush_ws(ws); + size_hint += self.write_block(buf, Some(name))?; + self.prepare_ws(ws); } Node::Include(ws, path) => { - size_hint += self.handle_include(ctx, buf, ws, path)?; + self.flush_ws(ws); + size_hint += self.handle_include(ctx, buf, path)?; + self.prepare_ws(ws); } Node::Call( ws, @@ -689,7 +693,9 @@ impl<'a> Generator<'a> { ref args, }, ) => { - size_hint += self.write_call(ctx, buf, ws, scope, name, args)?; + self.flush_ws(ws); + size_hint += self.write_call(ctx, buf, scope, name, args)?; + self.prepare_ws(ws); } Node::Macro(ws, _) => { if level != AstLevel::Top { @@ -925,13 +931,12 @@ impl<'a> Generator<'a> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - ws: Ws, scope: Option<&str>, name: &str, args: &[Expr<'_>], ) -> Result { if name == "super" { - return self.write_block(buf, None, ws); + return self.write_block(buf, None); } let (def, own_ctx) = match scope { @@ -957,7 +962,6 @@ impl<'a> Generator<'a> { } }; - self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first self.locals.push(); self.write_buf_writable(buf)?; buf.writeln("{")?; @@ -1018,7 +1022,6 @@ impl<'a> Generator<'a> { size_hint += self.write_buf_writable(buf)?; buf.writeln("}")?; self.locals.pop(); - self.prepare_ws(ws); Ok(size_hint) } @@ -1026,10 +1029,8 @@ impl<'a> Generator<'a> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - ws: Ws, path: &str, ) -> Result { - self.flush_ws(ws); self.write_buf_writable(buf)?; let path = self .input @@ -1057,7 +1058,6 @@ impl<'a> Generator<'a> { size_hint += gen.write_buf_writable(buf)?; size_hint }; - self.prepare_ws(ws); Ok(size_hint) } @@ -1140,11 +1140,7 @@ impl<'a> Generator<'a> { &mut self, buf: &mut Buffer, name: Option<&'a str>, - outer: Ws, ) -> Result { - // Flush preceding whitespace according to the outer WS spec - self.flush_ws(outer); - let prev_block = self.super_block; let cur = match (name, prev_block) { // The top-level context contains a block definition @@ -1190,10 +1186,8 @@ impl<'a> Generator<'a> { self.locals.pop(); self.flush_ws(*ws); - // Restore original block context and set whitespace suppression for - // succeeding whitespace according to the outer WS spec + // Restore original block context self.super_block = prev_block; - self.prepare_ws(outer); Ok(size_hint) } From 15c4e8201e15aa301d732731f74ebf473db9d812 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 10:31:30 -0500 Subject: [PATCH 25/27] Inline handle_ws in Generator::handle --- askama_derive/src/generator.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 4ffbb543f..53268a602 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -701,7 +701,8 @@ impl<'a> Generator<'a> { if level != AstLevel::Top { return Err("macro blocks only allowed at the top level".into()); } - self.handle_ws(ws); + self.flush_ws(ws); + self.prepare_ws(ws); } Node::Raw(ws, ref raw) => { self.flush_ws(ws); @@ -712,7 +713,8 @@ impl<'a> Generator<'a> { if level != AstLevel::Top { return Err("import blocks only allowed at the top level".into()); } - self.handle_ws(ws); + self.flush_ws(ws); + self.prepare_ws(ws); } Node::Extends(_) => { if level != AstLevel::Top { @@ -722,12 +724,14 @@ impl<'a> Generator<'a> { // except for the blocks defined in it. } Node::Break(ws) => { - self.handle_ws(ws); + self.flush_ws(ws); + self.prepare_ws(ws); self.write_buf_writable(buf)?; buf.writeln("break;")?; } Node::Continue(ws) => { - self.handle_ws(ws); + self.flush_ws(ws); + self.prepare_ws(ws); self.write_buf_writable(buf)?; buf.writeln("continue;")?; } @@ -1870,13 +1874,6 @@ impl<'a> Generator<'a> { /* Helper methods for dealing with whitespace nodes */ - // Combines `flush_ws()` and `prepare_ws()` to handle both trailing whitespace from the - // preceding literal and leading whitespace from the succeeding literal. - fn handle_ws(&mut self, ws: Ws) { - self.flush_ws(ws); - self.prepare_ws(ws); - } - fn should_trim_ws(&self, ws: Option) -> WhitespaceHandling { match ws { Some(Whitespace::Suppress) => WhitespaceHandling::Suppress, From 3a37e8c40f6fee50622307121175e79a3db4a8c9 Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 11:00:34 -0500 Subject: [PATCH 26/27] Don't destructure Call in Generator::handle --- askama_derive/src/generator.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 53268a602..dd4cd5d2f 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -685,16 +685,9 @@ impl<'a> Generator<'a> { size_hint += self.handle_include(ctx, buf, path)?; self.prepare_ws(ws); } - Node::Call( - ws, - Call { - scope, - name, - ref args, - }, - ) => { + Node::Call(ws, ref call) => { self.flush_ws(ws); - size_hint += self.write_call(ctx, buf, scope, name, args)?; + size_hint += self.write_call(ctx, buf, call)?; self.prepare_ws(ws); } Node::Macro(ws, _) => { @@ -935,15 +928,15 @@ impl<'a> Generator<'a> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - scope: Option<&str>, - name: &str, - args: &[Expr<'_>], + call: &'a Call<'_>, ) -> Result { - if name == "super" { + let Call { scope, name, args } = call; + + if *name == "super" { return self.write_block(buf, None); } - let (def, own_ctx) = match scope { + let (def, own_ctx) = match *scope { Some(s) => { let path = ctx.imports.get(s).ok_or_else(|| { CompileError::from(format!("no import found for scope {s:?}")) From 981fb6b7e8efe2239e6337f7b789894bb72d359a Mon Sep 17 00:00:00 2001 From: Andrew Dona-Couch Date: Mon, 6 Mar 2023 11:12:28 -0500 Subject: [PATCH 27/27] Don't destructure Match in Generator::handle --- askama_derive/src/generator.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index dd4cd5d2f..97a608e15 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -665,9 +665,9 @@ impl<'a> Generator<'a> { size_hint += self.write_cond(ctx, buf, conds)?; self.prepare_ws(ws); } - Node::Match(ws, Match { ref expr, ref arms }) => { + Node::Match(ws, ref match_node) => { self.flush_ws(ws); - size_hint += self.write_match(ctx, buf, expr, arms)?; + size_hint += self.write_match(ctx, buf, match_node)?; self.prepare_ws(ws); } Node::Loop(ws, ref loop_block) => { @@ -815,9 +815,10 @@ impl<'a> Generator<'a> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - expr: &Expr<'_>, - arms: &'a [When<'_>], + match_node: &'a Match<'_>, ) -> Result { + let Match { expr, arms } = match_node; + let flushed = self.write_buf_writable(buf)?; let mut arm_sizes = Vec::new();