mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-26 13:45:13 -07:00
Add support for parenthesized expressions
This commit is contained in:
+14
-1
@@ -100,7 +100,7 @@ module Liquid
|
|||||||
when :number
|
when :number
|
||||||
number
|
number
|
||||||
when :open_round
|
when :open_round
|
||||||
range_lookup
|
grouping_or_range_lookup
|
||||||
else
|
else
|
||||||
raise SyntaxError, "#{token} is not a valid expression"
|
raise SyntaxError, "#{token} is not a valid expression"
|
||||||
end
|
end
|
||||||
@@ -131,6 +131,19 @@ module Liquid
|
|||||||
VariableLookup.new(name, lookups, command_flags)
|
VariableLookup.new(name, lookups, command_flags)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parenthesized expressions are recursive
|
||||||
|
def grouping_or_range_lookup
|
||||||
|
consume(:open_round)
|
||||||
|
expr = expression
|
||||||
|
if consume?(:dotdot)
|
||||||
|
RangeLookup.create(expr, expression)
|
||||||
|
else
|
||||||
|
expr
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
consume(:close_round)
|
||||||
|
end
|
||||||
|
|
||||||
def range_lookup
|
def range_lookup
|
||||||
consume(:open_round)
|
consume(:open_round)
|
||||||
first = expression
|
first = expression
|
||||||
|
|||||||
@@ -35,13 +35,26 @@ class AssignTest < Minitest::Test
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_assign_boolean_expression_assignment
|
||||||
|
assert_template_result(
|
||||||
|
'it rendered',
|
||||||
|
<<~LIQUID,
|
||||||
|
{%- assign should_render = a == 0 or (b == 1 and c == 2) -%}
|
||||||
|
{%- if should_render -%}
|
||||||
|
it rendered
|
||||||
|
{%- endif -%}
|
||||||
|
LIQUID
|
||||||
|
{ 'b' => 1, 'c' => 2 },
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def test_assign_syntax_error
|
def test_assign_syntax_error
|
||||||
assert_match_syntax_error(/assign/, '{% assign foo not values %}.')
|
assert_match_syntax_error(/assign/, '{% assign foo not values %}.')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assign_throws_on_unsupported_syntax
|
def test_assign_throws_on_unsupported_syntax
|
||||||
assert_match_syntax_error(
|
assert_match_syntax_error(
|
||||||
"Expected dotdot but found pipe",
|
"Expected close_round but found pipe",
|
||||||
"{% assign foo = ('X' | downcase) %}",
|
"{% assign foo = ('X' | downcase) %}",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,11 +35,10 @@ class ParsingQuirksTest < Minitest::Test
|
|||||||
assert_raises(Liquid::SyntaxError) { Template.parse("{{test |a|b|}}") }
|
assert_raises(Liquid::SyntaxError) { Template.parse("{{test |a|b|}}") }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_meaningless_parens_error
|
def test_supported_parens
|
||||||
assert_raises(SyntaxError) do
|
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
||||||
markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
|
out = Template.parse("{% if #{markup} %} YES {% endif %}").render({ 'b' => 'bar', 'c' => 'baz' })
|
||||||
Template.parse("{% if #{markup} %} YES {% endif %}")
|
assert_equal(' YES ', out)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unexpected_characters_syntax_error
|
def test_unexpected_characters_syntax_error
|
||||||
|
|||||||
@@ -98,12 +98,12 @@ class ParserUnitTest < Minitest::Test
|
|||||||
expr = p.expression
|
expr = p.expression
|
||||||
assert(expr.is_a?(BinaryExpression))
|
assert(expr.is_a?(BinaryExpression))
|
||||||
assert_equal('and', expr.operator)
|
assert_equal('and', expr.operator)
|
||||||
assert_equal('==', expr.left_node.operator)
|
assert_equal('==', expr.left_node.operator)
|
||||||
assert_equal('a', expr.left_node.left_node.name)
|
assert_equal('a', expr.left_node.left_node.name)
|
||||||
assert_equal('b', expr.left_node.right_node.name)
|
assert_equal('b', expr.left_node.right_node.name)
|
||||||
assert_equal('or', expr.right_node.operator)
|
assert_equal('or', expr.right_node.operator)
|
||||||
assert_equal('c', expr.right_node.left_node.name)
|
assert_equal('c', expr.right_node.left_node.name)
|
||||||
assert_equal('d', expr.right_node.right_node.name)
|
assert_equal('d', expr.right_node.right_node.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_equality
|
def test_equality
|
||||||
@@ -197,6 +197,28 @@ class ParserUnitTest < Minitest::Test
|
|||||||
assert_equal('(hi[5].wat..old)', p.expression_string)
|
assert_equal('(hi[5].wat..old)', p.expression_string)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_groupings_aka_parenthesized_expressions
|
||||||
|
# without the parens, this would be evaled as a and (b or c)
|
||||||
|
p = new_parser("(a and b) or c")
|
||||||
|
expr = p.expression
|
||||||
|
assert_equal('or', expr.operator)
|
||||||
|
assert_equal('and', expr.left_node.operator)
|
||||||
|
assert_equal('a', expr.left_node.left_node.name)
|
||||||
|
assert_equal('b', expr.left_node.right_node.name)
|
||||||
|
assert_equal('c', expr.right_node.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_groupings_can_be_used_to_hijack_operation_priority
|
||||||
|
# without parens would be parsed as `a and (b == c)`
|
||||||
|
p = new_parser("(a and b) == c")
|
||||||
|
expr = p.expression
|
||||||
|
assert_equal('==', expr.operator)
|
||||||
|
assert_equal('and', expr.left_node.operator)
|
||||||
|
assert_equal('a', expr.left_node.left_node.name)
|
||||||
|
assert_equal('b', expr.left_node.right_node.name)
|
||||||
|
assert_equal('c', expr.right_node.name)
|
||||||
|
end
|
||||||
|
|
||||||
def test_argument_string
|
def test_argument_string
|
||||||
p = new_parser("filter: hi.there[5], keyarg: 7")
|
p = new_parser("filter: hi.there[5], keyarg: 7")
|
||||||
assert_equal('filter', p.consume(:id))
|
assert_equal('filter', p.consume(:id))
|
||||||
|
|||||||
Reference in New Issue
Block a user