liquid_interpreter/
variable.rs1use 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#[derive(Clone, Debug, PartialEq)]
12pub struct Variable {
13 variable: Scalar,
14 indexes: Vec<Expression>,
15}
16
17impl Variable {
18 pub fn with_literal<S: Into<Scalar>>(value: S) -> Self {
20 Self {
21 variable: value.into(),
22 indexes: Default::default(),
23 }
24 }
25
26 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 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 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}