Skip to content

Commit

Permalink
Implement `for … in … if …
Browse files Browse the repository at this point in the history
  • Loading branch information
Kijewski committed Jul 30, 2021
1 parent 3ae5234 commit 1c22e3f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 33 deletions.
76 changes: 47 additions & 29 deletions askama_shared/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,28 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
Node::Match(ws1, ref expr, ref arms, ws2) => {
self.write_match(ctx, buf, ws1, expr, arms, ws2)?;
}
Node::Loop(ws1, ref var, ref iter, ref body, ws2, ref else_block, ws3) => {
self.write_loop(ctx, buf, ws1, var, iter, body, ws2, else_block, ws3)?;
Node::Loop(
ws1,
ref var,
ref iter,
ref cond,
ref body,
ws2,
ref else_block,
ws3,
) => {
self.write_loop(
ctx,
buf,
ws1,
var,
iter,
cond.as_ref(),
body,
ws2,
else_block,
ws3,
)?;
}
Node::BlockDef(ws1, name, _, ws2) => {
self.write_block(buf, Some(name), Ws(ws1.0, ws2.1))?;
Expand Down Expand Up @@ -591,55 +611,53 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
ws1: Ws,
var: &'a Target<'_>,
iter: &Expr<'_>,
cond: Option<&Expr<'_>>,
body: &'a [Node<'_>],
ws2: Ws,
else_block: &'a [Node<'_>],
ws3: Ws,
) -> Result<usize, CompileError> {
self.handle_ws(ws1);
self.locals.push();

let expr_code = self.visit_expr_root(iter)?;

let flushed = self.write_buf_writable(buf)?;
buf.writeln("{")?;
buf.writeln("let mut _did_not_loop = true;")?;
buf.write("for (");
self.visit_target(buf, true, true, var);
match iter {
Expr::Range(_, _, _) => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new({}) {{",
expr_code
)),
Expr::Array(..) => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new({}.iter()) {{",
expr_code
)),
Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {};", expr_code)),
Expr::Array(..) => buf.writeln(&format!("let _iter = {}.iter();", expr_code)),
// If `iter` is a call then we assume it's something that returns
// an iterator. If not then the user can explicitly add the needed
// call without issues.
Expr::MethodCall(..) | Expr::PathCall(..) | Expr::Index(..) => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new(({}).into_iter()) {{",
expr_code
)),
Expr::MethodCall(..) | Expr::PathCall(..) | Expr::Index(..) => {
buf.writeln(&format!("let _iter = ({}).into_iter();", expr_code))
}
// If accessing `self` then it most likely needs to be
// borrowed, to prevent an attempt of moving.
_ if expr_code.starts_with("self.") => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new(((&{}).into_iter())) {{",
expr_code
)),
_ if expr_code.starts_with("self.") => {
buf.writeln(&format!("let _iter = (&{}).into_iter();", expr_code))
}
// If accessing a field then it most likely needs to be
// borrowed, to prevent an attempt of moving.
Expr::Attr(..) => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new(((&{}).into_iter())) {{",
expr_code
)),
Expr::Attr(..) => buf.writeln(&format!("let _iter = (&{}).into_iter();", expr_code)),
// Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
_ => buf.writeln(&format!(
", _loop_item) in ::askama::helpers::TemplateLoop::new(({}).into_iter()) {{",
expr_code
)),
_ => buf.writeln(&format!("let _iter = ({}).into_iter();", expr_code)),
}?;
if let Some(cond) = cond {
self.locals.push();
buf.write("let _iter = _iter.filter(|");
self.visit_target(buf, true, true, var);
buf.write("| -> bool {");
self.visit_expr(buf, cond)?;
buf.writeln("});")?;
self.locals.pop();
}

self.locals.push();
buf.write("for (");
self.visit_target(buf, true, true, var);
buf.writeln(", _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {")?;

buf.writeln("_did_not_loop = false;")?;
let mut size_hint1 = self.handle(ctx, body, buf, AstLevel::Nested)?;
Expand Down
2 changes: 1 addition & 1 deletion askama_shared/src/heritage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl<'a> Context<'a> {
nested.push(nodes);
}
}
Node::Loop(_, _, _, nodes, _, else_block, _) => {
Node::Loop(_, _, _, _, nodes, _, else_block, _) => {
nested.push(nodes);
nested.push(else_block);
}
Expand Down
16 changes: 13 additions & 3 deletions askama_shared/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub enum Node<'a> {
Ws,
Target<'a>,
Expr<'a>,
Option<Expr<'a>>,
Vec<Node<'a>>,
Ws,
Vec<Node<'a>>,
Expand Down Expand Up @@ -872,6 +873,7 @@ fn block_let(i: &[u8]) -> IResult<&[u8], Node<'_>> {
}

fn block_for<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> {
let if_cond = preceded(ws(tag("if")), cut(ws(expr_any)));
let else_block = preceded(
ws(tag("else")),
cut(tuple((
Expand All @@ -890,6 +892,7 @@ fn block_for<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>>
ws(tag("in")),
cut(tuple((
ws(expr_any),
opt(if_cond),
opt(tag("-")),
|i| tag_block_end(i, s),
cut(tuple((
Expand All @@ -905,19 +908,26 @@ fn block_for<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>>
))),
))),
));
let (i, (pws1, _, (var, _, (iter, nws1, _, (block, (_, pws2, else_block, _, nws2)))))) = p(i)?;
let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (block, (_, pws2, else_block, _, nws2)))))) =
p(i)?;
match else_block {
None => {
let ws1 = Ws(pws1.is_some(), nws1.is_some());
let ws2 = Ws(pws2.is_some(), false);
let ws3 = Ws(false, nws2.is_some());
Ok((i, Node::Loop(ws1, var, iter, block, ws2, Vec::new(), ws3)))
Ok((
i,
Node::Loop(ws1, var, iter, cond, block, ws2, Vec::new(), ws3),
))
}
Some((nws3, _, else_block, _, pws3)) => {
let ws1 = Ws(pws1.is_some(), nws1.is_some());
let ws2 = Ws(pws2.is_some(), nws3.is_some());
let ws3 = Ws(pws3.is_some(), nws2.is_some());
Ok((i, Node::Loop(ws1, var, iter, block, ws2, else_block, ws3)))
Ok((
i,
Node::Loop(ws1, var, iter, cond, block, ws2, else_block, ws3),
))
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions testing/tests/loops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,18 @@ fn test_for_enumerate() {
};
assert_eq!(t.render().unwrap(), "0=hello-1=world-2=!-");
}

#[derive(Template)]
#[template(
source = "{% for i in 0..limit if i % 2 == 0 %}{{i}}.{% endfor %}",
ext = "txt"
)]
struct ForInIf {
limit: usize,
}

#[test]
fn test_for_in_if() {
let t = ForInIf { limit: 10 };
assert_eq!(t.render().unwrap(), "0.2.4.6.8.");
}

0 comments on commit 1c22e3f

Please sign in to comment.