From 3bddd26aae5a6fec7d59b4460936f6bdf7f108a7 Mon Sep 17 00:00:00 2001 From: Ilkka Markkinen Date: Sun, 15 Sep 2024 11:13:58 +0300 Subject: [PATCH 1/4] Allow index access to root object --- crates/core/src/model/find.rs | 4 ++ crates/core/src/parser/grammar.pest | 8 +++- crates/core/src/parser/parser.rs | 53 ++++++++++++++++++++++--- crates/core/src/runtime/variable.rs | 30 ++++++++++++-- src/parser.rs | 1 + tests/conformance_ruby/variable_test.rs | 42 +++++++++++++++++++- 6 files changed, 127 insertions(+), 11 deletions(-) diff --git a/crates/core/src/model/find.rs b/crates/core/src/model/find.rs index fb8051c29..d6c687359 100644 --- a/crates/core/src/model/find.rs +++ b/crates/core/src/model/find.rs @@ -18,6 +18,10 @@ use super::ValueView; pub struct Path<'s>(Vec>); impl<'s> Path<'s> { + pub fn empty() -> Self { + Path(vec![]) + } + /// Create a `Value` reference. pub fn with_index>>(value: I) -> Self { let indexes = vec![value.into()]; diff --git a/crates/core/src/parser/grammar.pest b/crates/core/src/parser/grammar.pest index ecc678495..4303fc37a 100644 --- a/crates/core/src/parser/grammar.pest +++ b/crates/core/src/parser/grammar.pest @@ -29,7 +29,13 @@ Raw = @{ (!(TagStart | ExpressionStart) ~ ANY)+ } // Inner parsing Identifier = @{ (ASCII_ALPHA | "_" | NON_WHITESPACE_CONTROL_HYPHEN) ~ (ASCII_ALPHANUMERIC | "_" | NON_WHITESPACE_CONTROL_HYPHEN)* } -Variable = ${ Identifier +// For root level hash access we'd need to accept that there might not be an Identifier preceding a hash access (the brackets with a value inside) +// Variable = ${ Identifier +// ~ ( ("." ~ Identifier) +// | ("[" ~ WHITESPACE* ~ Value ~ WHITESPACE* ~ "]") +// )* +// } +Variable = ${ ( Identifier | ("[" ~ WHITESPACE* ~ Value ~ WHITESPACE* ~ "]") ) ~ ( ("." ~ Identifier) | ("[" ~ WHITESPACE* ~ Value ~ WHITESPACE* ~ "]") )* diff --git a/crates/core/src/parser/parser.rs b/crates/core/src/parser/parser.rs index a1dbed61e..8a2f5977d 100644 --- a/crates/core/src/parser/parser.rs +++ b/crates/core/src/parser/parser.rs @@ -142,11 +142,22 @@ fn parse_variable_pair(variable: Pair) -> Variable { let mut indexes = variable.into_inner(); let first_identifier = indexes - .next() - .expect("A variable starts with an identifier.") - .as_str() - .to_owned(); - let mut variable = Variable::with_literal(first_identifier); + .peek() + .expect("A variable starts with an identifier or an index"); + + let mut variable = match first_identifier.as_rule() { + Rule::Identifier => { + indexes.next(); + Variable::with_literal((first_identifier.as_str().to_owned())) + } + Rule::Value => Variable::empty(), + _ => unreachable!(), + }; + // let a = first_identifier + // .expect("A variable starts with an identifier.") + // .as_str() + // .to_owned(); + // let mut variable = Variable::with_literal(first_identifier.unwrap()); let indexes = indexes.map(|index| match index.as_rule() { Rule::Identifier => Expression::with_literal(index.as_str().to_owned()), @@ -1153,6 +1164,38 @@ mod test { assert_eq!(parse_variable_pair(variable), expected); } + #[test] + fn test_parse_variable_pair_with_root_index_literals() { + let variable = LiquidParser::parse(Rule::Variable, "['bar']") + .unwrap() + .next() + .unwrap(); + + let indexes: Vec = vec![Expression::Literal(Value::scalar("bar"))]; + + let mut expected = Variable::empty(); + expected.extend(indexes); + + assert_eq!(parse_variable_pair(variable), expected); + } + + #[test] + fn test_parse_variable_pair_with_root_index_variable() { + let variable = LiquidParser::parse(Rule::Variable, "[foo.bar]") + .unwrap() + .next() + .unwrap(); + + let indexes = vec![Expression::Variable( + Variable::with_literal("foo").push_literal("bar"), + )]; + + let mut expected = Variable::empty(); + expected.extend(indexes); + + assert_eq!(parse_variable_pair(variable), expected); + } + #[test] fn test_whitespace_control() { let options = Language::default(); diff --git a/crates/core/src/runtime/variable.rs b/crates/core/src/runtime/variable.rs index 60a8f663b..9f2d5094e 100644 --- a/crates/core/src/runtime/variable.rs +++ b/crates/core/src/runtime/variable.rs @@ -11,15 +11,22 @@ use super::Runtime; /// A `Value` reference. #[derive(Clone, Debug, PartialEq)] pub struct Variable { - variable: Scalar, + variable: Option, indexes: Vec, } impl Variable { + pub fn empty() -> Self { + Self { + variable: None, + indexes: Default::default(), + } + } + /// Create a `Value` reference. pub fn with_literal>(value: S) -> Self { Self { - variable: value.into(), + variable: Some(value.into()), indexes: Default::default(), } } @@ -32,7 +39,17 @@ impl Variable { /// Convert to a `Path`. pub fn try_evaluate<'c>(&'c self, runtime: &'c dyn Runtime) -> Option> { - let mut path = Path::with_index(self.variable.as_ref()); + let mut path = if self.variable.is_some() { + let v = self.variable.as_ref().unwrap(); + Path::with_index(v.clone()) + } else { + Path::empty() + }; + + // let mut path = match self.variable { + // Some(v) => Path::with_index(v.as_ref()), + // None => Path::empty(), + // }; path.reserve(self.indexes.len()); for expr in &self.indexes { let v = expr.try_evaluate(runtime)?; @@ -47,7 +64,12 @@ impl Variable { /// Convert to a `Path`. pub fn evaluate<'c>(&'c self, runtime: &'c dyn Runtime) -> Result> { - let mut path = Path::with_index(self.variable.as_ref()); + let mut path = if self.variable.is_some() { + let v = self.variable.as_ref().unwrap(); + Path::with_index(v.clone()) + } else { + Path::empty() + }; path.reserve(self.indexes.len()); for expr in &self.indexes { let v = expr.evaluate(runtime)?; diff --git a/src/parser.rs b/src/parser.rs index 212f03841..983134dfe 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -242,6 +242,7 @@ impl Parser { /// pub fn parse(&self, text: &str) -> Result