Skip to content

Commit 43268c6

Browse files
committed
feat(grammar): Support for empty/blank literals
Fixes cobalt-org#222 BREAKING CHANGE: `empty` and `blank` can no longer be variable names.
1 parent 7d3b0e5 commit 43268c6

File tree

10 files changed

+136
-24
lines changed

10 files changed

+136
-24
lines changed

liquid-compiler/src/grammar.pest

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ FilterChain = { Value ~ ("|" ~ Filter)* }
3535

3636
// Literals
3737
NilLiteral = @{ "nil" | "null" }
38+
EmptyLiteral = @{ "empty" }
39+
BlankLiteral = @{ "blank" }
3840
StringLiteral = @{ ("'" ~ (!"'" ~ ANY)* ~ "'")
3941
| ("\"" ~ (!"\"" ~ ANY)* ~ "\"") }
4042

@@ -43,7 +45,7 @@ FloatLiteral = @{ ("+" | "-")? ~ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ }
4345

4446
BooleanLiteral = @{ "true" | "false" }
4547

46-
Literal = { NilLiteral | StringLiteral | FloatLiteral | IntegerLiteral | BooleanLiteral }
48+
Literal = { NilLiteral | EmptyLiteral | BlankLiteral | StringLiteral | FloatLiteral | IntegerLiteral | BooleanLiteral }
4749

4850
Range = { "(" ~ Value ~ ".." ~ Value ~ ")" }
4951

liquid-compiler/src/parser.rs

+14
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ fn parse_literal(literal: Pair) -> Value {
9797

9898
match literal.as_rule() {
9999
Rule::NilLiteral => Value::Nil,
100+
Rule::EmptyLiteral => Value::Empty,
101+
Rule::BlankLiteral => Value::Blank,
100102
Rule::StringLiteral => {
101103
let literal = literal.as_str();
102104
let trim_quotes = &literal[1..literal.len() - 1];
@@ -871,6 +873,18 @@ mod test {
871873
.unwrap();
872874
assert_eq!(parse_literal(nil), Value::Nil);
873875

876+
let blank = LiquidParser::parse(Rule::Literal, "blank")
877+
.unwrap()
878+
.next()
879+
.unwrap();
880+
assert_eq!(parse_literal(blank), Value::Blank);
881+
882+
let empty = LiquidParser::parse(Rule::Literal, "empty")
883+
.unwrap()
884+
.next()
885+
.unwrap();
886+
assert_eq!(parse_literal(empty), Value::Empty);
887+
874888
let integer = LiquidParser::parse(Rule::Literal, "42")
875889
.unwrap()
876890
.next()

liquid-value/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extern crate itertools;
1111
extern crate liquid_error;
1212
extern crate num_traits;
1313

14+
#[macro_use]
1415
mod macros;
1516

1617
pub mod map;

liquid-value/src/macros.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -191,32 +191,32 @@ macro_rules! value_internal {
191191
//////////////////////////////////////////////////////////////////////////
192192

193193
(nil) => {
194-
::liquid_value::Value::Nil
194+
$crate::Value::Nil
195195
};
196196

197197
(true) => {
198-
::liquid_value::Value::scalar(true)
198+
$crate::Value::scalar(true)
199199
};
200200

201201
(false) => {
202-
::liquid_value::Value::scalar(false)
202+
$crate::Value::scalar(false)
203203
};
204204

205205
([]) => {
206-
::liquid_value::Value::Array(value_internal_vec![])
206+
$crate::Value::Array(value_internal_vec![])
207207
};
208208

209209
([ $($tt:tt)+ ]) => {
210-
::liquid_value::Value::Array(value_internal!(@array [] $($tt)+))
210+
$crate::Value::Array(value_internal!(@array [] $($tt)+))
211211
};
212212

213213
({}) => {
214-
::liquid_value::Value::Object(Default::default())
214+
$crate::Value::Object(Default::default())
215215
};
216216

217217
({ $($tt:tt)+ }) => {
218-
::liquid_value::Value::Object({
219-
let mut object = ::liquid_value::Object::new();
218+
$crate::Value::Object({
219+
let mut object = $crate::Object::new();
220220
value_internal!(@object object () ($($tt)+) ($($tt)+));
221221
object
222222
})
@@ -229,7 +229,7 @@ macro_rules! value_internal {
229229
// Any Serialize type: numbers, strings, struct literals, variables etc.
230230
// Must be below every other rule.
231231
($other:expr) => {
232-
::liquid_value::to_value($other).unwrap()
232+
$crate::to_value($other).unwrap()
233233
};
234234
}
235235

liquid-value/src/values.rs

+104-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ pub enum Value {
1818
Object(Object),
1919
/// Nothing.
2020
Nil,
21+
/// No content.
22+
Empty,
23+
/// Evaluates to empty string.
24+
Blank,
2125
}
2226

2327
/// Type representing a Liquid array, payload of the `Value::Array` variant
@@ -55,7 +59,7 @@ impl Value {
5559
let arr: Vec<String> = x.iter().map(|(k, v)| format!("{}: {}", k, v)).collect();
5660
borrow::Cow::Owned(arr.join(", "))
5761
}
58-
Value::Nil => borrow::Cow::Borrowed(""),
62+
Value::Nil | Value::Empty | Value::Blank => borrow::Cow::Borrowed(""),
5963
}
6064
}
6165

@@ -154,12 +158,44 @@ impl Value {
154158
}
155159
}
156160

161+
/// Extracts the empty value if it is empty
162+
pub fn as_empty(&self) -> Option<()> {
163+
match *self {
164+
Value::Empty => Some(()),
165+
_ => None,
166+
}
167+
}
168+
169+
/// Tests whether this value is empty
170+
pub fn is_empty(&self) -> bool {
171+
match *self {
172+
Value::Empty => true,
173+
_ => false,
174+
}
175+
}
176+
177+
/// Extracts the blank value if it is blank
178+
pub fn as_blank(&self) -> Option<()> {
179+
match *self {
180+
Value::Blank => Some(()),
181+
_ => None,
182+
}
183+
}
184+
185+
/// Tests whether this value is blank
186+
pub fn is_blank(&self) -> bool {
187+
match *self {
188+
Value::Blank => true,
189+
_ => false,
190+
}
191+
}
192+
157193
/// Evaluate using Liquid "truthiness"
158194
pub fn is_truthy(&self) -> bool {
159195
// encode Ruby truthiness: all values except false and nil are true
160196
match *self {
161197
Value::Scalar(ref x) => x.is_truthy(),
162-
Value::Nil => false,
198+
Value::Nil | Value::Empty | Value::Blank => false,
163199
_ => true,
164200
}
165201
}
@@ -169,6 +205,8 @@ impl Value {
169205
match *self {
170206
Value::Scalar(ref x) => x.is_default(),
171207
Value::Nil => true,
208+
Value::Empty => true,
209+
Value::Blank => true,
172210
Value::Array(ref x) => x.is_empty(),
173211
Value::Object(ref x) => x.is_empty(),
174212
}
@@ -179,6 +217,8 @@ impl Value {
179217
match *self {
180218
Value::Scalar(ref x) => x.type_name(),
181219
Value::Nil => "nil",
220+
Value::Empty => "empty",
221+
Value::Blank => "blank",
182222
Value::Array(_) => "array",
183223
Value::Object(_) => "object",
184224
}
@@ -316,7 +356,37 @@ fn value_eq(lhs: &Value, rhs: &Value) -> bool {
316356
(&Value::Scalar(ref x), &Value::Scalar(ref y)) => x == y,
317357
(&Value::Array(ref x), &Value::Array(ref y)) => x == y,
318358
(&Value::Object(ref x), &Value::Object(ref y)) => x == y,
319-
(&Value::Nil, &Value::Nil) => true,
359+
(&Value::Nil, &Value::Nil) |
360+
(&Value::Empty, &Value::Empty) |
361+
(&Value::Blank, &Value::Blank) |
362+
(&Value::Empty, &Value::Blank) |
363+
(&Value::Blank, &Value::Empty) => true,
364+
365+
// encode a best-guess of empty rules
366+
// See tables in https://stackoverflow.com/questions/885414/a-concise-explanation-of-nil-v-empty-v-blank-in-ruby-on-rails
367+
(&Value::Empty, &Value::Scalar(ref s)) | (&Value::Scalar(ref s), &Value::Empty) => {
368+
s.to_str().is_empty()
369+
}
370+
(&Value::Empty, &Value::Array(ref s)) | (&Value::Array(ref s), &Value::Empty) => {
371+
s.is_empty()
372+
}
373+
(&Value::Empty, &Value::Object(ref s)) | (&Value::Object(ref s), &Value::Empty) => {
374+
s.is_empty()
375+
}
376+
377+
// encode a best-guess of blank rules
378+
// See tables in https://stackoverflow.com/questions/885414/a-concise-explanation-of-nil-v-empty-v-blank-in-ruby-on-rails
379+
(&Value::Nil, &Value::Blank) |
380+
(&Value::Blank, &Value::Nil) => true,
381+
(&Value::Blank, &Value::Scalar(ref s)) | (&Value::Scalar(ref s), &Value::Blank) => {
382+
s.to_str().trim().is_empty() || ! s.to_bool().unwrap_or(true)
383+
}
384+
(&Value::Blank, &Value::Array(ref s)) | (&Value::Array(ref s), &Value::Blank) => {
385+
s.is_empty()
386+
}
387+
(&Value::Blank, &Value::Object(ref s)) | (&Value::Object(ref s), &Value::Blank) => {
388+
s.is_empty()
389+
}
320390

321391
// encode Ruby truthiness: all values except false and nil are true
322392
(&Value::Nil, &Value::Scalar(ref b)) | (&Value::Scalar(ref b), &Value::Nil) => {
@@ -445,4 +515,35 @@ mod test {
445515
assert!(Value::scalar(true) != Value::Nil);
446516
assert!(Value::scalar("") != Value::Nil);
447517
}
518+
519+
#[test]
520+
fn empty_equality() {
521+
// Truth table from https://stackoverflow.com/questions/885414/a-concise-explanation-of-nil-v-empty-v-blank-in-ruby-on-rails
522+
assert_eq!(Value::Empty, Value::Empty);
523+
assert_eq!(Value::Empty, Value::Blank);
524+
assert_eq!(Value::Empty, liquid_value!(""));
525+
assert_ne!(Value::Empty, liquid_value!(" "));
526+
assert_eq!(Value::Empty, liquid_value!([]));
527+
assert_ne!(Value::Empty, liquid_value!([nil]));
528+
assert_eq!(Value::Empty, liquid_value!({}));
529+
assert_ne!(Value::Empty, liquid_value!({"a": nil}));
530+
}
531+
532+
#[test]
533+
fn blank_equality() {
534+
// Truth table from https://stackoverflow.com/questions/885414/a-concise-explanation-of-nil-v-empty-v-blank-in-ruby-on-rails
535+
assert_eq!(Value::Blank, Value::Blank);
536+
assert_eq!(Value::Blank, Value::Empty);
537+
assert_eq!(Value::Blank, liquid_value!(nil));
538+
assert_eq!(Value::Blank, liquid_value!(false));
539+
assert_ne!(Value::Blank, liquid_value!(true));
540+
assert_ne!(Value::Blank, liquid_value!(0));
541+
assert_ne!(Value::Blank, liquid_value!(1));
542+
assert_eq!(Value::Blank, liquid_value!(""));
543+
assert_eq!(Value::Blank, liquid_value!(" "));
544+
assert_eq!(Value::Blank, liquid_value!([]));
545+
assert_ne!(Value::Blank, liquid_value!([nil]));
546+
assert_eq!(Value::Blank, liquid_value!({}));
547+
assert_ne!(Value::Blank, liquid_value!({"a": nil}));
548+
}
448549
}

src/tags/for_block.rs

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ impl For {
122122
fn get_array(context: &Context, array_id: &Expression) -> Result<Vec<Value>> {
123123
let array = array_id.evaluate(context)?;
124124
match array {
125+
Value::Empty => Ok(vec![]),
125126
Value::Array(x) => Ok(x.to_owned()),
126127
Value::Object(x) => {
127128
let x = x

tests/conformance_ruby/tags/for_tag_test.rs

-1
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,6 @@ fn test_for_parentloop_nil_when_not_present() {
485485
}
486486

487487
#[test]
488-
#[should_panic] // liquid-rust#222
489488
fn test_inner_for_over_empty_input() {
490489
assert_template_result!(
491490
"oo",

tests/conformance_ruby/tags/standard_tag_test.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -430,13 +430,11 @@ fn test_size_of_hash() {
430430
}
431431

432432
#[test]
433-
#[should_panic] // liquid-rust#222
434433
fn test_illegal_symbols() {
435-
// Implementation specific: strict_variables is enabled, testing that instead.
436-
assert_render_error!("{% if true == empty %}?{% endif %}");
437-
assert_render_error!("{% if true == null %}?{% endif %}");
438-
assert_render_error!("{% if empty == true %}?{% endif %}");
439-
assert_render_error!("{% if null == true %}?{% endif %}");
434+
assert_template_result!("", "{% if true == empty %}?{% endif %}");
435+
assert_template_result!("", "{% if true == null %}?{% endif %}");
436+
assert_template_result!("", "{% if empty == true %}?{% endif %}");
437+
assert_template_result!("", "{% if null == true %}?{% endif %}");
440438
}
441439

442440
#[test]

tests/conformance_ruby/tags/statements_test.rs

-2
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,12 @@ fn test_var_and_long_string_are_equal_backwards() {
9595
*/
9696

9797
#[test]
98-
#[should_panic] // liquid-rust#222
9998
fn test_is_collection_empty() {
10099
let text = " {% if array == empty %} true {% else %} false {% endif %} ";
101100
assert_template_result!(" true ", text, v!({"array": []}));
102101
}
103102

104103
#[test]
105-
#[should_panic] // liquid-rust#222
106104
fn test_is_not_collection_empty() {
107105
let text = " {% if array == empty %} true {% else %} false {% endif %} ";
108106
assert_template_result!(" false ", text, v!({"array": [1, 2, 3]}));

tests/conformance_ruby/variable_test.rs

-2
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,11 @@ fn test_ignore_unknown() {
3636
}
3737

3838
#[test]
39-
#[should_panic] // liquid-rust#222
4039
fn test_using_blank_as_variable_name() {
4140
assert_template_result!(r#""#, r#"{% assign foo = blank %}{{ foo }}"#);
4241
}
4342

4443
#[test]
45-
#[should_panic] // liquid-rust#222
4644
fn test_using_empty_as_variable_name() {
4745
assert_template_result!(r#""#, r#"{% assign foo = empty %}{{ foo }}"#);
4846
}

0 commit comments

Comments
 (0)