From 647c22b3a900c184ac795b92dfb2e4f0cd19c730 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 6 Apr 2020 22:02:10 -0400 Subject: [PATCH 1/8] Initial scaffold for partials --- src/parser/parser.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 49ffe64..d5c904e 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -30,6 +30,7 @@ enum DustTag<'a> { DTNotExists(Container<'a>), DTBlock(NamedBlock<'a>), DTInlinePartial(NamedBlock<'a>), + DTPartial(ParameterizedBlock<'a>), } #[derive(Clone, Debug, PartialEq)] @@ -86,6 +87,12 @@ struct NamedBlock<'a> { contents: Option>, } +#[derive(Clone, Debug, PartialEq)] +struct ParameterizedBlock<'a> { + name: String, + contents: Option>, +} + #[derive(Clone, Debug, PartialEq)] struct Body<'a> { elements: Vec>, @@ -115,6 +122,7 @@ fn dust_tag(i: &str) -> IResult<&str, DustTag> { conditional("{^", DustTag::DTNotExists), named_block("{+", DustTag::DTBlock), named_block("{<", DustTag::DTInlinePartial), + parameterized_self_closing_block("{>", DustTag::DTPartial), ))(i) } @@ -290,6 +298,26 @@ where } } +fn parameterized_self_closing_block<'a, F>( + open_matcher: &'static str, + constructor: F, +) -> impl Fn(&'a str) -> IResult<&'a str, DustTag<'a>> +where + F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>, +{ + move |i: &'a str| { + let (i, name) = delimited(tag(open_matcher), key, tag("/}"))(i)?; + + Ok(( + i, + constructor(ParameterizedBlock { + name: name.to_owned(), + contents: None, + }), + )) + } +} + fn filter(i: &str) -> IResult<&str, Filter> { preceded( tag("|"), @@ -620,4 +648,18 @@ mod tests { Ok(("", r#"foo"bar\baz"#.to_owned())) ); } + + #[test] + fn test_self_closing_unquoted_partial() { + assert_eq!( + super::dust_tag("{>foo/}"), + Ok(( + "", + DustTag::DTPartial(ParameterizedBlock { + name: "foo".to_owned(), + contents: None + }) + )) + ); + } } From ffce2b05696f3219b1a42cbaf25f2a8d393d85ad Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 6 Apr 2020 22:47:24 -0400 Subject: [PATCH 2/8] The extra steps are making it harder to debug, so move parser directly into test --- src/parser/parser.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index d5c904e..f86b13b 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -6,7 +6,9 @@ use nom::bytes::complete::is_not; use nom::bytes::complete::tag; use nom::bytes::complete::take_until; use nom::character::complete::one_of; +use nom::character::complete::space1; use nom::combinator::map; +use nom::combinator::not; use nom::combinator::opt; use nom::combinator::recognize; use nom::combinator::rest; @@ -91,6 +93,7 @@ struct NamedBlock<'a> { struct ParameterizedBlock<'a> { name: String, contents: Option>, + params: Vec<&'a str>, } #[derive(Clone, Debug, PartialEq)] @@ -306,13 +309,21 @@ where F: Fn(ParameterizedBlock<'a>) -> DustTag<'a>, { move |i: &'a str| { - let (i, name) = delimited(tag(open_matcher), key, tag("/}"))(i)?; + let (i, (name, params)) = delimited( + tag(open_matcher), + tuple(( + key, + opt(preceded(space1, separated_list(space1, is_not(" ")))), + )), + tag("/}"), + )(i)?; Ok(( i, constructor(ParameterizedBlock { name: name.to_owned(), contents: None, + params: params.unwrap_or(Vec::new()), }), )) } @@ -652,14 +663,12 @@ mod tests { #[test] fn test_self_closing_unquoted_partial() { assert_eq!( - super::dust_tag("{>foo/}"), - Ok(( - "", - DustTag::DTPartial(ParameterizedBlock { - name: "foo".to_owned(), - contents: None - }) - )) + delimited( + tag("{>"), + tuple((key, opt(preceded(space1, separated_list(space1, key))),)), + tag("/}"), + )("{>foo bar/}"), + Ok(("", ("foo", Some(vec!["bar"])))) ); } } From 8641e5a98b2d893e3d9fb29a9bce18408c46b841 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 6 Apr 2020 23:21:03 -0400 Subject: [PATCH 3/8] Starting stand-alone parsers for key value pairs --- src/parser/parser.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index f86b13b..b4cee94 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -19,6 +19,7 @@ use nom::multi::many1; use nom::multi::separated_list; use nom::sequence::delimited; use nom::sequence::preceded; +use nom::sequence::separated_pair; use nom::sequence::tuple; use nom::IResult; @@ -166,6 +167,15 @@ fn path(i: &str) -> IResult<&str, Path> { map(separated_list(tag("."), key), |body| Path { keys: body })(i) } +/// Either a literal or a path to a value +fn rvalue(i: &str) -> IResult<&str, &str> { + key(i) +} + +fn key_value_pair(i: &str) -> IResult<&str, (&str, Path)> { + separated_pair(key, tag("="), path)(i) +} + /// Display a value from the context fn reference(i: &str) -> IResult<&str, Reference> { let (remaining, (p, filters)) = delimited(tag("{"), tuple((path, many0(filter))), tag("}"))(i)?; @@ -665,10 +675,13 @@ mod tests { assert_eq!( delimited( tag("{>"), - tuple((key, opt(preceded(space1, separated_list(space1, key))),)), + tuple(( + key, + opt(preceded(space1, separated_list(space1, key_value_pair))), + )), tag("/}"), - )("{>foo bar/}"), - Ok(("", ("foo", Some(vec!["bar"])))) + )("{>foo bar=baz/}"), + Ok(("", ("foo", Some(vec![("bar", Path { keys: vec!["baz"] })])))) ); } } From dd8b4ac28c8be9c8a7bce8e3ced7e245608484ce Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 6 Apr 2020 23:30:42 -0400 Subject: [PATCH 4/8] Introducing an RValue enum to handle paths vs literals --- src/parser/parser.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index b4cee94..4241b23 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -113,6 +113,12 @@ enum TemplateElement<'a> { TETag(DustTag<'a>), } +#[derive(Clone, Debug, PartialEq)] +enum RValue<'a> { + RVPath(Path<'a>), + RVLiteral(&'a str), +} + /// Any element significant to dust that isn't plain text /// /// These elements are always wrapped in curly braces @@ -168,12 +174,13 @@ fn path(i: &str) -> IResult<&str, Path> { } /// Either a literal or a path to a value -fn rvalue(i: &str) -> IResult<&str, &str> { - key(i) +fn rvalue(i: &str) -> IResult<&str, RValue> { + map(path, RValue::RVPath)(i) } -fn key_value_pair(i: &str) -> IResult<&str, (&str, Path)> { - separated_pair(key, tag("="), path)(i) +/// Parameters for a partial +fn key_value_pair(i: &str) -> IResult<&str, (&str, RValue)> { + separated_pair(key, tag("="), rvalue)(i) } /// Display a value from the context @@ -681,7 +688,13 @@ mod tests { )), tag("/}"), )("{>foo bar=baz/}"), - Ok(("", ("foo", Some(vec![("bar", Path { keys: vec!["baz"] })])))) + Ok(( + "", + ( + "foo", + Some(vec![("bar", RValue::RVPath(Path { keys: vec!["baz"] }))]) + ) + )) ); } } From 79100fc50f4faf700e29eee6c146914c4d54dcd8 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 6 Apr 2020 23:35:09 -0400 Subject: [PATCH 5/8] Introducing a KVPair type --- src/parser/parser.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 4241b23..aaf384d 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -119,6 +119,12 @@ enum RValue<'a> { RVLiteral(&'a str), } +#[derive(Clone, Debug, PartialEq)] +struct KVPair<'a> { + key: &'a str, + value: RValue<'a>, +} + /// Any element significant to dust that isn't plain text /// /// These elements are always wrapped in curly braces @@ -179,8 +185,11 @@ fn rvalue(i: &str) -> IResult<&str, RValue> { } /// Parameters for a partial -fn key_value_pair(i: &str) -> IResult<&str, (&str, RValue)> { - separated_pair(key, tag("="), rvalue)(i) +fn key_value_pair(i: &str) -> IResult<&str, KVPair> { + map(separated_pair(key, tag("="), rvalue), |(k, v)| KVPair { + key: k, + value: v, + })(i) } /// Display a value from the context @@ -692,7 +701,10 @@ mod tests { "", ( "foo", - Some(vec![("bar", RValue::RVPath(Path { keys: vec!["baz"] }))]) + Some(vec![KVPair { + key: "bar", + value: RValue::RVPath(Path { keys: vec!["baz"] }) + }]) ) )) ); From 8bfa622c4ccb79166d92e1f7a0b0439ef346ea5e Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Mon, 6 Apr 2020 23:47:50 -0400 Subject: [PATCH 6/8] parsing out string literals in addition to paths --- src/parser/parser.rs | 57 +++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index aaf384d..d724ead 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -1,5 +1,4 @@ use nom::branch::alt; -use nom::bytes::complete::escaped; use nom::bytes::complete::escaped_transform; use nom::bytes::complete::is_a; use nom::bytes::complete::is_not; @@ -8,7 +7,6 @@ use nom::bytes::complete::take_until; use nom::character::complete::one_of; use nom::character::complete::space1; use nom::combinator::map; -use nom::combinator::not; use nom::combinator::opt; use nom::combinator::recognize; use nom::combinator::rest; @@ -17,6 +15,7 @@ use nom::combinator::verify; use nom::multi::many0; use nom::multi::many1; use nom::multi::separated_list; +use nom::multi::separated_nonempty_list; use nom::sequence::delimited; use nom::sequence::preceded; use nom::sequence::separated_pair; @@ -97,6 +96,18 @@ struct ParameterizedBlock<'a> { params: Vec<&'a str>, } +#[derive(Clone, Debug, PartialEq)] +enum RValue<'a> { + RVPath(Path<'a>), + RVString(String), +} + +#[derive(Clone, Debug, PartialEq)] +struct KVPair<'a> { + key: &'a str, + value: RValue<'a>, +} + #[derive(Clone, Debug, PartialEq)] struct Body<'a> { elements: Vec>, @@ -113,18 +124,6 @@ enum TemplateElement<'a> { TETag(DustTag<'a>), } -#[derive(Clone, Debug, PartialEq)] -enum RValue<'a> { - RVPath(Path<'a>), - RVLiteral(&'a str), -} - -#[derive(Clone, Debug, PartialEq)] -struct KVPair<'a> { - key: &'a str, - value: RValue<'a>, -} - /// Any element significant to dust that isn't plain text /// /// These elements are always wrapped in curly braces @@ -176,12 +175,17 @@ fn key(i: &str) -> IResult<&str, &str> { /// A series of keys separated by '.' to reference a variable in the context fn path(i: &str) -> IResult<&str, Path> { - map(separated_list(tag("."), key), |body| Path { keys: body })(i) + map(separated_nonempty_list(tag("."), key), |body| Path { + keys: body, + })(i) } /// Either a literal or a path to a value fn rvalue(i: &str) -> IResult<&str, RValue> { - map(path, RValue::RVPath)(i) + alt(( + map(path, RValue::RVPath), + map(quoted_string, RValue::RVString), + ))(i) } /// Parameters for a partial @@ -708,5 +712,26 @@ mod tests { ) )) ); + + assert_eq!( + delimited( + tag("{>"), + tuple(( + key, + opt(preceded(space1, separated_list(space1, key_value_pair))), + )), + tag("/}"), + )(r#"{>foo bar="ba\"z"/}"#), + Ok(( + "", + ( + "foo", + Some(vec![KVPair { + key: "bar", + value: RValue::RVString("ba\"z".to_owned()) + }]) + ) + )) + ); } } From e2f03de2975346fc8af990e2a4d8e3d129c6465e Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Tue, 7 Apr 2020 19:51:06 -0400 Subject: [PATCH 7/8] Integrated the partial parser into the rest of the grammar --- src/parser/parser.rs | 62 ++++++++++++++------------------------------ 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index d724ead..828991f 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -92,8 +92,7 @@ struct NamedBlock<'a> { #[derive(Clone, Debug, PartialEq)] struct ParameterizedBlock<'a> { name: String, - contents: Option>, - params: Vec<&'a str>, + params: Vec>, } #[derive(Clone, Debug, PartialEq)] @@ -342,8 +341,8 @@ where let (i, (name, params)) = delimited( tag(open_matcher), tuple(( - key, - opt(preceded(space1, separated_list(space1, is_not(" ")))), + alt((map(key, String::from), quoted_string)), + opt(preceded(space1, separated_list(space1, key_value_pair))), )), tag("/}"), )(i)?; @@ -351,8 +350,7 @@ where Ok(( i, constructor(ParameterizedBlock { - name: name.to_owned(), - contents: None, + name: name, params: params.unwrap_or(Vec::new()), }), )) @@ -691,46 +689,24 @@ mod tests { } #[test] - fn test_self_closing_unquoted_partial() { + fn test_unquoted_partial() { assert_eq!( - delimited( - tag("{>"), - tuple(( - key, - opt(preceded(space1, separated_list(space1, key_value_pair))), - )), - tag("/}"), - )("{>foo bar=baz/}"), + dust_tag(r#"{>foo bar=baz animal="cat"/}"#), Ok(( "", - ( - "foo", - Some(vec![KVPair { - key: "bar", - value: RValue::RVPath(Path { keys: vec!["baz"] }) - }]) - ) - )) - ); - - assert_eq!( - delimited( - tag("{>"), - tuple(( - key, - opt(preceded(space1, separated_list(space1, key_value_pair))), - )), - tag("/}"), - )(r#"{>foo bar="ba\"z"/}"#), - Ok(( - "", - ( - "foo", - Some(vec![KVPair { - key: "bar", - value: RValue::RVString("ba\"z".to_owned()) - }]) - ) + DustTag::DTPartial(ParameterizedBlock { + name: "foo".to_owned(), + params: vec![ + KVPair { + key: "bar", + value: RValue::RVPath(Path { keys: vec!["baz"] }) + }, + KVPair { + key: "animal", + value: RValue::RVString("cat".to_owned()) + } + ] + }) )) ); } From 2ebe4798d5700ffbdb894e4fbec13b9c4ddcb816 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Tue, 7 Apr 2020 19:53:57 -0400 Subject: [PATCH 8/8] Add test for quoted partial --- src/parser/parser.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 828991f..e9e2683 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -710,4 +710,27 @@ mod tests { )) ); } + + #[test] + fn test_quoted_partial() { + assert_eq!( + dust_tag(r#"{>"template name * with * special \" characters" bar=baz animal="cat"/}"#), + Ok(( + "", + DustTag::DTPartial(ParameterizedBlock { + name: r#"template name * with * special " characters"#.to_owned(), + params: vec![ + KVPair { + key: "bar", + value: RValue::RVPath(Path { keys: vec!["baz"] }) + }, + KVPair { + key: "animal", + value: RValue::RVString("cat".to_owned()) + } + ] + }) + )) + ); + } }