From 7e227907fe5d293b2d7620602d48ffba94e35dc5 Mon Sep 17 00:00:00 2001 From: René Kijewski Date: Thu, 1 Jul 2021 18:29:48 +0200 Subject: Implement destructoring of structs This PR implements the destructoring of structs on the lhs of "let" and "for" statements. --- askama_shared/src/generator.rs | 19 ++++++- askama_shared/src/parser.rs | 48 ++++++++++++++--- testing/tests/let_destructoring.rs | 107 +++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 testing/tests/let_destructoring.rs diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs index a7f52ca..36d1841 100644 --- a/askama_shared/src/generator.rs +++ b/askama_shared/src/generator.rs @@ -824,9 +824,12 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { _ => false, } } - Target::Tuple(targets) => targets + Target::Tuple(_, targets) => targets .iter() .any(|target| self.is_shadowing_variable(target)), + Target::Struct(_, named_targets) => named_targets + .iter() + .any(|(_, target)| self.is_shadowing_variable(target)), } } @@ -1521,7 +1524,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } buf.write(name); } - Target::Tuple(targets) => { + Target::Tuple(path, targets) => { + buf.write(&path.join("::")); buf.write("("); for target in targets { self.visit_target(buf, initialized, target); @@ -1529,6 +1533,17 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> { } buf.write(")"); } + Target::Struct(path, targets) => { + buf.write(&path.join("::")); + buf.write(" { "); + for (name, target) in targets { + buf.write(normalize_identifier(name)); + buf.write(": "); + self.visit_target(buf, initialized, target); + buf.write(","); + } + buf.write(" }"); + } } } diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index a1aabe7..261887b 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -4,7 +4,7 @@ use nom::character::complete::{anychar, char, digit1}; use nom::combinator::{complete, map, opt, recognize, value}; use nom::error::{Error, ParseError}; use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; -use nom::sequence::{delimited, pair, preceded, tuple}; +use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::{self, error_position, Compare, IResult, InputTake}; use std::str; @@ -138,7 +138,8 @@ pub struct Macro<'a> { #[derive(Debug, PartialEq)] pub enum Target<'a> { Name(&'a str), - Tuple(Vec>), + Tuple(Vec<&'a str>, Vec>), + Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>), } #[derive(Clone, Copy, Debug, PartialEq)] @@ -404,15 +405,21 @@ fn variant_path(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> { })(i) } +fn named_target(i: &[u8]) -> IResult<&[u8], (&str, Target<'_>)> { + let (i, (src, target)) = pair(identifier, opt(preceded(ws(tag(":")), target)))(i)?; + Ok((i, (src, target.unwrap_or(Target::Name(src))))) +} + fn target(i: &[u8]) -> IResult<&[u8], Target<'_>> { let mut opt_opening_paren = map(opt(ws(tag("("))), |o| o.is_some()); let mut opt_closing_paren = map(opt(ws(tag(")"))), |o| o.is_some()); + // match tuples and unused parentheses let (i, target_is_tuple) = opt_opening_paren(i)?; if target_is_tuple { let (i, is_empty_tuple) = opt_closing_paren(i)?; if is_empty_tuple { - return Ok((i, Target::Tuple(vec![]))); + return Ok((i, Target::Tuple(Vec::new(), Vec::new()))); } let (i, first_target) = target(i)?; @@ -429,10 +436,39 @@ fn target(i: &[u8]) -> IResult<&[u8], Target<'_>> { opt(ws(tag(","))), ws(tag(")")), ))(i)?; - Ok((i, Target::Tuple(targets))) - } else { - map(identifier, Target::Name)(i) + return Ok((i, Target::Tuple(Vec::new(), targets))); + } + + // match structs + let (i, path) = opt(path)(i)?; + if let Some(path) = path { + let (i, is_unnamed_struct) = opt_opening_paren(i)?; + if is_unnamed_struct { + let (i, targets) = alt(( + map(tag(")"), |_| Vec::new()), + terminated( + separated_list1(ws(tag(",")), target), + pair(opt(ws(tag(","))), ws(tag(")"))), + ), + ))(i)?; + return Ok((i, Target::Tuple(path, targets))); + } else { + let (i, targets) = preceded( + ws(tag("{")), + alt(( + map(tag("}"), |_| Vec::new()), + terminated( + separated_list1(ws(tag(",")), named_target), + pair(opt(ws(tag(","))), ws(tag("}"))), + ), + )), + )(i)?; + return Ok((i, Target::Struct(path, targets))); + } } + + // neither nor struct + map(identifier, Target::Name)(i) } fn variant_name(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> { diff --git a/testing/tests/let_destructoring.rs b/testing/tests/let_destructoring.rs new file mode 100644 index 0000000..20f7fff --- /dev/null +++ b/testing/tests/let_destructoring.rs @@ -0,0 +1,107 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = "{% let (a, b, c) = v %}{{a}}{{b}}{{c}}", ext = "txt")] +struct LetDestructoringTuple { + v: (i32, i32, i32), +} + +#[test] +fn test_let_destruct_tuple() { + let t = LetDestructoringTuple { v: (1, 2, 3) }; + assert_eq!(t.render().unwrap(), "123"); +} + +struct UnnamedStruct(i32, i32, i32); + +#[derive(Template)] +#[template( + source = "{% let UnnamedStruct(a, b, c) = v %}{{a}}{{b}}{{c}}", + ext = "txt" +)] +struct LetDestructoringUnnamedStruct { + v: UnnamedStruct, +} + +#[test] +fn test_let_destruct_unnamed_struct() { + let t = LetDestructoringUnnamedStruct { + v: UnnamedStruct(1, 2, 3), + }; + assert_eq!(t.render().unwrap(), "123"); +} + +#[derive(Template)] +#[template( + source = "{% let UnnamedStruct(a, b, c) = v %}{{a}}{{b}}{{c}}", + ext = "txt" +)] +struct LetDestructoringUnnamedStructRef<'a> { + v: &'a UnnamedStruct, +} + +#[test] +fn test_let_destruct_unnamed_struct_ref() { + let v = UnnamedStruct(1, 2, 3); + let t = LetDestructoringUnnamedStructRef { v: &v }; + assert_eq!(t.render().unwrap(), "123"); +} + +struct NamedStruct { + a: i32, + b: i32, + c: i32, +} + +#[derive(Template)] +#[template( + source = "{% let NamedStruct { a, b: d, c } = v %}{{a}}{{d}}{{c}}", + ext = "txt" +)] +struct LetDestructoringNamedStruct { + v: NamedStruct, +} + +#[test] +fn test_let_destruct_named_struct() { + let t = LetDestructoringNamedStruct { + v: NamedStruct { a: 1, b: 2, c: 3 }, + }; + assert_eq!(t.render().unwrap(), "123"); +} + +#[derive(Template)] +#[template( + source = "{% let NamedStruct { a, b: d, c } = v %}{{a}}{{d}}{{c}}", + ext = "txt" +)] +struct LetDestructoringNamedStructRef<'a> { + v: &'a NamedStruct, +} + +#[test] +fn test_let_destruct_named_struct_ref() { + let v = NamedStruct { a: 1, b: 2, c: 3 }; + let t = LetDestructoringNamedStructRef { v: &v }; + assert_eq!(t.render().unwrap(), "123"); +} + +mod some { + pub mod path { + pub struct Struct<'a>(pub &'a str); + } +} + +#[derive(Template)] +#[template(source = "{% let some::path::Struct(v) = v %}{{v}}", ext = "txt")] +struct LetDestructoringWithPath<'a> { + v: some::path::Struct<'a>, +} + +#[test] +fn test_let_destruct_with_path() { + let t = LetDestructoringWithPath { + v: some::path::Struct("hello"), + }; + assert_eq!(t.render().unwrap(), "hello"); +} -- cgit