liquid_interpreter/
variable.rs

1use std::fmt;
2
3use liquid_error::{Error, Result};
4use liquid_value::Path;
5use liquid_value::Scalar;
6
7use super::Context;
8use super::Expression;
9
10/// A `Value` reference.
11#[derive(Clone, Debug, PartialEq)]
12pub struct Variable {
13    variable: Scalar,
14    indexes: Vec<Expression>,
15}
16
17impl Variable {
18    /// Create a `Value` reference.
19    pub fn with_literal<S: Into<Scalar>>(value: S) -> Self {
20        Self {
21            variable: value.into(),
22            indexes: Default::default(),
23        }
24    }
25
26    /// Append a literal.
27    pub fn push_literal<S: Into<Scalar>>(mut self, value: S) -> Self {
28        self.indexes.push(Expression::with_literal(value));
29        self
30    }
31
32    /// Convert to a `Path`.
33    pub fn try_evaluate<'c>(&'c self, context: &'c Context) -> Option<Path<'c>> {
34        let mut path = Path::with_index(self.variable.as_ref());
35        path.reserve(self.indexes.len());
36        for expr in &self.indexes {
37            let v = expr.try_evaluate(context)?;
38            let s = v.as_scalar()?.as_ref();
39            path.push(s);
40        }
41        Some(path)
42    }
43
44    /// Convert to a `Path`.
45    pub fn evaluate<'c>(&'c self, context: &'c Context) -> Result<Path<'c>> {
46        let mut path = Path::with_index(self.variable.as_ref());
47        path.reserve(self.indexes.len());
48        for expr in &self.indexes {
49            let v = expr.evaluate(context)?;
50            let s = v
51                .as_scalar()
52                .ok_or_else(|| Error::with_msg(format!("Expected scalar, found `{}`", v.source())))?
53                .as_ref();
54            path.push(s);
55        }
56        Ok(path)
57    }
58}
59
60impl Extend<Scalar> for Variable {
61    fn extend<T: IntoIterator<Item = Scalar>>(&mut self, iter: T) {
62        let path = iter.into_iter().map(Expression::with_literal);
63        self.indexes.extend(path);
64    }
65}
66
67impl Extend<Expression> for Variable {
68    fn extend<T: IntoIterator<Item = Expression>>(&mut self, iter: T) {
69        let path = iter.into_iter();
70        self.indexes.extend(path);
71    }
72}
73
74impl fmt::Display for Variable {
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        write!(f, "{}", self.variable.render())?;
77        for index in self.indexes.iter() {
78            write!(f, "[{}]", index)?;
79        }
80        Ok(())
81    }
82}
83
84#[cfg(test)]
85mod test {
86    use super::*;
87
88    use liquid_value::Object;
89    use serde_yaml;
90
91    use super::super::ContextBuilder;
92
93    #[test]
94    fn identifier_path_array_index() {
95        let globals: Object = serde_yaml::from_str(
96            r#"
97test_a: ["test"]
98"#,
99        )
100        .unwrap();
101        let mut var = Variable::with_literal("test_a");
102        let index = vec![Scalar::new(0)];
103        var.extend(index);
104
105        let context = ContextBuilder::new().set_globals(&globals).build();
106        let actual = var.evaluate(&context).unwrap();
107        let actual = context.stack().get(&actual).unwrap();
108        assert_eq!(actual.to_str(), "test");
109    }
110
111    #[test]
112    fn identifier_path_array_index_negative() {
113        let globals: Object = serde_yaml::from_str(
114            r#"
115test_a: ["test1", "test2"]
116"#,
117        )
118        .unwrap();
119        let mut var = Variable::with_literal("test_a");
120        let index = vec![Scalar::new(-1)];
121        var.extend(index);
122
123        let context = ContextBuilder::new().set_globals(&globals).build();
124        let actual = var.evaluate(&context).unwrap();
125        let actual = context.stack().get(&actual).unwrap();
126        assert_eq!(actual.to_str(), "test2");
127    }
128
129    #[test]
130    fn identifier_path_object() {
131        let globals: Object = serde_yaml::from_str(
132            r#"
133test_a:
134  - test_h: 5
135"#,
136        )
137        .unwrap();
138        let mut var = Variable::with_literal("test_a");
139        let index = vec![Scalar::new(0), Scalar::new("test_h")];
140        var.extend(index);
141
142        let context = ContextBuilder::new().set_globals(&globals).build();
143        let actual = var.evaluate(&context).unwrap();
144        let actual = context.stack().get(&actual).unwrap();
145        assert_eq!(actual.to_str(), "5");
146    }
147}