Skip to content

Commit 0cacc01

Browse files
karreiroalbchu
authored andcommittedMar 5, 2025
* Move expression handling from variable.rb to expression.rb
* Update test suite to validate parity * Remove parentheses handling * Split boolean into comparison and logical expressions
1 parent aff9a98 commit 0cacc01

7 files changed

+185
-426
lines changed
 

‎lib/liquid.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ module Liquid
8080
require 'liquid/range_lookup'
8181
require 'liquid/resource_limits'
8282
require 'liquid/expression'
83+
require 'liquid/expression/comparison_expression'
84+
require 'liquid/expression/logical_expression'
8385
require 'liquid/template'
8486
require 'liquid/condition'
8587
require 'liquid/utils'
@@ -89,4 +91,3 @@ module Liquid
8991
require 'liquid/usage'
9092
require 'liquid/registers'
9193
require 'liquid/template_factory'
92-
require 'liquid/boolean_expression'

‎lib/liquid/boolean_expression.rb

-129
This file was deleted.

‎lib/liquid/expression.rb

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ def parse(markup, ss = StringScanner.new(""), cache = nil)
5151
end
5252

5353
def inner_parse(markup, ss, cache)
54+
return LogicalExpression.parse(markup, ss, cache) if LogicalExpression.logical?(markup)
55+
return ComparisonExpression.parse(markup, ss, cache) if ComparisonExpression.comparison?(markup)
56+
5457
if (markup.start_with?("(") && markup.end_with?(")")) && markup =~ RANGES_REGEX
5558
return RangeLookup.parse(
5659
Regexp.last_match(1),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
module Liquid
4+
class Expression
5+
class ComparisonExpression
6+
COMPARISON_REGEX = /\A\s*(.+?)\s*(==|!=|<>|<=|>=|<|>|contains)\s*(.+)\s*\z/
7+
8+
class << self
9+
def comparison?(markup)
10+
markup.match(COMPARISON_REGEX)
11+
end
12+
13+
def parse(markup, ss, cache)
14+
match = comparison?(markup)
15+
16+
if match
17+
left = Expression.parse(match[1].strip, ss, cache)
18+
operator = match[2].strip
19+
right = Expression.parse(match[3].strip, ss, cache)
20+
21+
return Condition.new(left, operator, right)
22+
end
23+
24+
Condition.new(parse(markup, ss, cache), nil, nil)
25+
end
26+
end
27+
end
28+
end
29+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# frozen_string_literal: true
2+
3+
module Liquid
4+
class Expression
5+
class LogicalExpression
6+
LOGICAL_REGEX = /\A\s*(.+?)\s+(and|or)\s+(.+)\s*\z/i
7+
EXPRESSIONS_AND_OPERATORS = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
8+
BOOLEAN_OPERATORS = ['and', 'or'].freeze
9+
10+
class << self
11+
def logical?(markup)
12+
markup.match(LOGICAL_REGEX)
13+
end
14+
15+
def boolean_operator?(markup)
16+
BOOLEAN_OPERATORS.include?(markup)
17+
end
18+
19+
def parse(markup, ss, cache)
20+
expressions = markup.scan(EXPRESSIONS_AND_OPERATORS)
21+
22+
last_expr = expressions.pop
23+
24+
condition = if ComparisonExpression.comparison?(last_expr)
25+
ComparisonExpression.parse(last_expr, ss, cache)
26+
elsif logical?(last_expr)
27+
LogicalExpression.parse(last_expr, ss, cache)
28+
else
29+
Condition.new(Expression.parse(last_expr, ss, cache), nil, nil)
30+
end
31+
32+
until expressions.empty?
33+
operator = expressions.pop.to_s.strip
34+
next unless boolean_operator?(operator)
35+
36+
expr = expressions.pop.to_s.strip
37+
38+
new_condition = if ComparisonExpression.comparison?(expr)
39+
ComparisonExpression.parse(expr, ss, cache)
40+
elsif logical?(expr)
41+
LogicalExpression.parse(expr, ss, cache)
42+
else
43+
Condition.new(Expression.parse(expr, ss, cache), nil, nil)
44+
end
45+
46+
if operator == 'and'
47+
new_condition.and(condition)
48+
else # operator == 'or'
49+
new_condition.or(condition)
50+
end
51+
52+
condition = new_condition
53+
end
54+
55+
condition
56+
end
57+
end
58+
end
59+
end
60+
end

‎lib/liquid/variable.rb

+7-21
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ class Variable
1717
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
1818
JustTagAttributes = /\A#{TagAttributes}\z/o
1919
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
20-
ComparisonOperator = /==|!=|<>|<=|>=|<|>|contains/o
21-
LogicalOperator = /\s+(and|or)\s+/i
2220

2321
attr_accessor :filters, :name, :line_number
2422
attr_reader :parse_context
@@ -49,14 +47,7 @@ def lax_parse(markup)
4947

5048
name_markup = Regexp.last_match(1)
5149
filter_markup = Regexp.last_match(2)
52-
53-
# Check if name_markup contains a comparison operator or logical operator
54-
@name = if name_markup =~ LogicalOperator || name_markup =~ ComparisonOperator
55-
BooleanExpression.parse(name_markup)
56-
else
57-
parse_context.parse_expression(name_markup)
58-
end
59-
50+
@name = parse_context.parse_expression(name_markup)
6051
if filter_markup =~ FilterMarkupRegex
6152
filters = Regexp.last_match(1).scan(FilterParser)
6253
filters.each do |f|
@@ -74,18 +65,13 @@ def strict_parse(markup)
7465

7566
return if p.look(:end_of_string)
7667

77-
# Check if markup contains a comparison operator or logical operator
78-
if markup =~ LogicalOperator || markup =~ ComparisonOperator
79-
@name = BooleanExpression.parse(markup)
80-
else
81-
@name = parse_context.parse_expression(p.expression)
82-
while p.consume?(:pipe)
83-
filtername = p.consume(:id)
84-
filterargs = p.consume?(:colon) ? parse_filterargs(p) : Const::EMPTY_ARRAY
85-
@filters << parse_filter_expressions(filtername, filterargs)
86-
end
87-
p.consume(:end_of_string)
68+
@name = parse_context.parse_expression(p.expression)
69+
while p.consume?(:pipe)
70+
filtername = p.consume(:id)
71+
filterargs = p.consume?(:colon) ? parse_filterargs(p) : Const::EMPTY_ARRAY
72+
@filters << parse_filter_expressions(filtername, filterargs)
8873
end
74+
p.consume(:end_of_string)
8975
end
9076

9177
def parse_filterargs(p)

0 commit comments

Comments
 (0)
Failed to load comments.