diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index cdf5ebf75..97a608e15 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1,7 +1,10 @@ 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, BlockDef, Call, Cond, CondTest, Expr, Lit, Loop, Match, Node, Raw, Target, When, + Whitespace, Ws, +}; use crate::CompileError; use proc_macro::TokenStream; @@ -635,56 +638,76 @@ 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); + self.flush_ws(ws); + 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)?; + 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)?; + self.flush_ws(ws); + self.write_let(buf, var, val)?; + self.prepare_ws(ws); } - 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(ws1, ref expr, ref arms, ws2) => { - size_hint += self.write_match(ctx, buf, ws1, expr, arms, ws2)?; + Node::Match(ws, ref match_node) => { + self.flush_ws(ws); + size_hint += self.write_match(ctx, buf, match_node)?; + self.prepare_ws(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(ws1, name, _, ws2) => { - size_hint += self.write_block(buf, Some(name), Ws(ws1.0, ws2.1))?; + Node::BlockDef(ws, BlockDef { name, .. }) => { + 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, scope, name, ref args) => { - size_hint += self.write_call(ctx, buf, ws, scope, name, args)?; + Node::Call(ws, ref call) => { + self.flush_ws(ws); + size_hint += self.write_call(ctx, buf, call)?; + self.prepare_ws(ws); } - 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.flush_ws(ws); + self.prepare_ws(ws); } - Node::Raw(ws1, lws, val, rws, ws2) => { - self.handle_ws(ws1); - self.visit_lit(lws, val, rws); - 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 { 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 { @@ -694,12 +717,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;")?; } @@ -722,13 +747,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, &(cws, ref cond, ref nodes)) in conds.iter().enumerate() { - self.handle_ws(cws); + for (i, cond) in conds.iter().enumerate() { flushed += self.write_buf_writable(buf)?; if i > 0 { self.locals.pop(); @@ -736,7 +759,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 { @@ -771,10 +794,11 @@ impl<'a> Generator<'a> { buf.writeln(" {")?; - arm_size += self.handle(ctx, nodes, buf, AstLevel::Nested)?; + 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("}")?; @@ -791,12 +815,10 @@ impl<'a> Generator<'a> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - ws1: Ws, - expr: &Expr<'_>, - arms: &'a [When<'_>], - ws2: Ws, + match_node: &'a Match<'_>, ) -> Result { - self.flush_ws(ws1); + let Match { expr, arms } = match_node; + let flushed = self.write_buf_writable(buf)?; let mut arm_sizes = Vec::new(); @@ -805,8 +827,7 @@ 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; if i > 0 { arm_sizes.push(arm_size + self.write_buf_writable(buf)?); @@ -819,10 +840,11 @@ impl<'a> Generator<'a> { self.visit_target(buf, true, true, target); buf.writeln(" => {")?; - arm_size = self.handle(ctx, body, buf, AstLevel::Nested)?; + 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(); @@ -839,7 +861,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)?; @@ -883,16 +904,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("}")?; @@ -906,16 +929,15 @@ impl<'a> Generator<'a> { &mut self, ctx: &'a Context<'_>, buf: &mut Buffer, - ws: Ws, - scope: Option<&str>, - name: &str, - args: &[Expr<'_>], + call: &'a Call<'_>, ) -> Result { - if name == "super" { - return self.write_block(buf, None, ws); + 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:?}")) @@ -938,11 +960,10 @@ 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("{")?; - self.prepare_ws(def.ws1); + self.prepare_ws(def.ws); let mut names = Buffer::new(0); let mut values = Buffer::new(0); @@ -995,11 +1016,10 @@ 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(); - self.prepare_ws(ws); Ok(size_hint) } @@ -1007,10 +1027,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 @@ -1038,17 +1056,14 @@ impl<'a> Generator<'a> { size_hint += gen.write_buf_writable(buf)?; size_hint }; - self.prepare_ws(ws); Ok(size_hint) } 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); @@ -1093,11 +1108,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)?; @@ -1125,11 +1138,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 @@ -1160,16 +1169,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 @@ -1177,17 +1182,14 @@ 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 + // Restore original block context self.super_block = prev_block; - self.prepare_ws(outer); 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)); } @@ -1269,7 +1271,13 @@ impl<'a> Generator<'a> { Ok(size_hint) } - fn visit_lit(&mut self, lws: &'a str, val: &'a str, rws: &'a str) { + 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() { match self.skip_ws { @@ -1299,10 +1307,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 { @@ -1864,13 +1868,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, diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index dbb2b1fa0..eee476900 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::{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>, } @@ -64,8 +64,8 @@ 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))?; @@ -76,26 +76,27 @@ 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 (_, _, nodes) in branches { - nested.push(nodes); + Node::Cond(_, branches) => { + for Cond { block, .. } in branches { + nested.push(block); } } - Node::Loop(Loop { - body, else_block, .. - }) => { + Node::Loop( + _, + Loop { + body, else_block, .. + }, + ) => { nested.push(body); nested.push(else_block); } - Node::Match(_, _, arms, _) => { - for (_, _, arm) in arms { - nested.push(arm); + Node::Match(_, Match { arms, .. }) => { + for When { block, .. } in arms { + nested.push(block); } } _ => {} @@ -107,11 +108,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 79b178ef8..32c85398d 100644 --- a/askama_derive/src/parser/mod.rs +++ b/askama_derive/src/parser/mod.rs @@ -12,7 +12,10 @@ 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::{ + BlockDef, Call, Cond, CondTest, Lit, Loop, Macro, Match, Node, Raw, Target, When, Whitespace, + Ws, +}; use crate::config::Syntax; use crate::CompileError; @@ -111,11 +114,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. @@ -289,7 +294,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 8c122af5f..4b201444b 100644 --- a/askama_derive/src/parser/node.rs +++ b/askama_derive/src/parser/node.rs @@ -18,21 +18,21 @@ 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, Option<&'a str>, &'a str, Vec>), + Call(Ws, Call<'a>), LetDecl(Ws, Target<'a>), Let(Ws, Target<'a>, Expr<'a>), - Cond(Vec>, Ws), - Match(Ws, Expr<'a>, Vec>, Ws), - Loop(Loop<'a>), + Cond(Ws, Vec>), + Match(Ws, Match<'a>), + Loop(Ws, 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(&'a str, Macro<'a>), - Raw(Ws, &'a str, &'a str, &'a str, Ws), + Macro(Ws, Macro<'a>), + Raw(Ws, Raw<'a>), Break(Ws), Continue(Ws), } @@ -66,26 +66,76 @@ 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 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> { + /// 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>, +} + +/// 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>, +} + +/// 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, 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, } -pub(crate) type When<'a> = (Ws, Target<'a>, Vec>); - #[derive(Debug, PartialEq)] pub(crate) struct Macro<'a> { - pub(crate) ws1: Ws, + pub(crate) name: &'a str, pub(crate) args: Vec<&'a str>, pub(crate) nodes: Vec>, - pub(crate) ws2: Ws, + 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". @@ -94,7 +144,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> { @@ -140,7 +198,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<'_>> { @@ -171,8 +229,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>> { @@ -196,9 +261,27 @@ 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)))) + + 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>> { @@ -213,7 +296,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>> { @@ -229,7 +319,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>> { @@ -255,14 +352,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 (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), expr, arms, Ws(pws2, nws2)))) + 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<'_>> { @@ -341,16 +451,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), + }, + ), )) } @@ -378,11 +490,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), + }, + ), )) } @@ -441,12 +560,12 @@ fn block_macro<'a>(i: &'a str, s: &State<'_>) -> IResult<&'a str, Node<'a>> { Ok(( i, Node::Macro( - name, + Ws(pws1, nws2), Macro { - ws1: Ws(pws1, nws1), + name, args: params, nodes: contents, - ws2: Ws(pws2, nws2), + ws: Ws(pws2, nws1), }, ), )) @@ -472,13 +591,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 ws1 = Ws(pws1, nws1); - let ws2 = Ws(pws2, nws2); - Ok((i, Node::Raw(ws1, lws, val, rws, ws2))) + let lit = split_ws_parts(contents); + 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 91bb09ba8..d3d23e563 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, Target, 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] @@ -538,6 +532,37 @@ 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(Some(Whitespace::Suppress), Some(Whitespace::Minimize)), + target: Target::Path(vec!["Foo"]), + block: vec![], + }, + When { + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Preserve)), + target: Target::Name("_"), + block: vec![], + } + ], + } + )], + ); +} + #[test] fn test_parse_tuple() { use super::Expr::*; @@ -657,6 +682,42 @@ 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( + 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( + 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), + }, + )] + ); +} + #[test] fn test_missing_space_after_kw() { let syntax = Syntax::default(); @@ -666,3 +727,157 @@ fn test_missing_space_after_kw() { "unable to parse template:\n\n\"{%leta=b%}\"" )); } + +#[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), + 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), + Call { + scope: Some("foo"), + name: "bar", + args: vec![], + } + )], + ); +} + +#[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( + Ws(None, Some(Whitespace::Preserve)), + Macro { + name: "foo", + args: vec!["bar"], + nodes: vec![], + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + }, + )] + ); +} + +#[test] +fn test_parse_raw_block() { + use super::Raw; + let syntax = Syntax::default(); + assert_eq!( + super::parse( + "{% raw -%}{% if condition %}{{ result }}{% endif %}{%~ endraw +%}", + &syntax + ) + .unwrap(), + vec![Node::Raw( + Ws(None, Some(Whitespace::Preserve)), + Raw { + lit: Lit { + lws: "", + val: "{% if condition %}{{ result }}{% endif %}", + rws: "", + }, + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + } + )] + ); +} + +#[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::Preserve)), + BlockDef { + name: "foo", + block: vec![], + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + } + )], + ); +} + +#[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( + Ws(None, Some(Whitespace::Preserve)), + vec![Cond { + test: Some(CondTest { + expr: Expr::Var("condition"), + target: None, + }), + block: vec![], + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + },], + )], + ); + 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(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + },], + )], + ); + assert_eq!( + super::parse( + "{% if condition -%}{%+ else if other -%}{%~ else %}{%~ 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(Some(Whitespace::Preserve), Some(Whitespace::Suppress)), + }, + Cond { + test: Some(CondTest { + expr: Expr::Var("other"), + target: None, + }), + block: vec![], + ws: Ws(Some(Whitespace::Minimize), Some(Whitespace::Suppress)), + }, + Cond { + test: None, + block: vec![], + ws: Ws(Some(Whitespace::Minimize), None), + }, + ], + )], + ); +}