liquid 4.0.3 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +43 -0
  3. data/README.md +6 -0
  4. data/lib/liquid.rb +17 -5
  5. data/lib/liquid/block.rb +31 -14
  6. data/lib/liquid/block_body.rb +166 -54
  7. data/lib/liquid/condition.rb +39 -18
  8. data/lib/liquid/context.rb +106 -51
  9. data/lib/liquid/document.rb +47 -9
  10. data/lib/liquid/drop.rb +4 -2
  11. data/lib/liquid/errors.rb +20 -18
  12. data/lib/liquid/expression.rb +29 -34
  13. data/lib/liquid/extensions.rb +2 -0
  14. data/lib/liquid/file_system.rb +6 -4
  15. data/lib/liquid/forloop_drop.rb +11 -4
  16. data/lib/liquid/i18n.rb +5 -3
  17. data/lib/liquid/interrupts.rb +3 -1
  18. data/lib/liquid/lexer.rb +30 -23
  19. data/lib/liquid/locales/en.yml +3 -1
  20. data/lib/liquid/parse_context.rb +20 -4
  21. data/lib/liquid/parse_tree_visitor.rb +2 -2
  22. data/lib/liquid/parser.rb +30 -18
  23. data/lib/liquid/parser_switching.rb +17 -3
  24. data/lib/liquid/partial_cache.rb +24 -0
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/profiler/hooks.rb +26 -14
  27. data/lib/liquid/range_lookup.rb +5 -3
  28. data/lib/liquid/register.rb +6 -0
  29. data/lib/liquid/resource_limits.rb +47 -8
  30. data/lib/liquid/standardfilters.rb +67 -46
  31. data/lib/liquid/static_registers.rb +44 -0
  32. data/lib/liquid/strainer_factory.rb +36 -0
  33. data/lib/liquid/strainer_template.rb +53 -0
  34. data/lib/liquid/tablerowloop_drop.rb +6 -4
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tag/disableable.rb +22 -0
  37. data/lib/liquid/tag/disabler.rb +21 -0
  38. data/lib/liquid/tags/assign.rb +24 -10
  39. data/lib/liquid/tags/break.rb +8 -3
  40. data/lib/liquid/tags/capture.rb +11 -8
  41. data/lib/liquid/tags/case.rb +33 -27
  42. data/lib/liquid/tags/comment.rb +5 -3
  43. data/lib/liquid/tags/continue.rb +8 -3
  44. data/lib/liquid/tags/cycle.rb +25 -14
  45. data/lib/liquid/tags/decrement.rb +6 -3
  46. data/lib/liquid/tags/echo.rb +34 -0
  47. data/lib/liquid/tags/for.rb +68 -44
  48. data/lib/liquid/tags/if.rb +35 -23
  49. data/lib/liquid/tags/ifchanged.rb +11 -10
  50. data/lib/liquid/tags/include.rb +34 -47
  51. data/lib/liquid/tags/increment.rb +7 -3
  52. data/lib/liquid/tags/raw.rb +14 -11
  53. data/lib/liquid/tags/render.rb +84 -0
  54. data/lib/liquid/tags/table_row.rb +23 -19
  55. data/lib/liquid/tags/unless.rb +15 -15
  56. data/lib/liquid/template.rb +53 -72
  57. data/lib/liquid/template_factory.rb +9 -0
  58. data/lib/liquid/tokenizer.rb +17 -9
  59. data/lib/liquid/usage.rb +8 -0
  60. data/lib/liquid/utils.rb +5 -3
  61. data/lib/liquid/variable.rb +46 -41
  62. data/lib/liquid/variable_lookup.rb +8 -6
  63. data/lib/liquid/version.rb +2 -1
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +609 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +343 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +118 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +43 -32
  99. data/test/test_helper.rb +75 -22
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +9 -2
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +73 -47
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/lib/liquid/truffle.rb +0 -5
  123. data/test/truffle/truffle_test.rb +0 -9
  124. data/test/unit/context_unit_test.rb +0 -489
  125. data/test/unit/strainer_unit_test.rb +0 -164
data/lib/liquid/errors.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Error < ::StandardError
3
5
  attr_accessor :line_number
@@ -5,7 +7,7 @@ module Liquid
5
7
  attr_accessor :markup_context
6
8
 
7
9
  def to_s(with_prefix = true)
8
- str = ""
10
+ str = +""
9
11
  str << message_prefix if with_prefix
10
12
  str << super()
11
13
 
@@ -20,11 +22,11 @@ module Liquid
20
22
  private
21
23
 
22
24
  def message_prefix
23
- str = ""
24
- if is_a?(SyntaxError)
25
- str << "Liquid syntax error"
25
+ str = +""
26
+ str << if is_a?(SyntaxError)
27
+ "Liquid syntax error"
26
28
  else
27
- str << "Liquid error"
29
+ "Liquid error"
28
30
  end
29
31
 
30
32
  if line_number
@@ -38,19 +40,19 @@ module Liquid
38
40
  end
39
41
  end
40
42
 
41
- ArgumentError = Class.new(Error)
42
- ContextError = Class.new(Error)
43
- FileSystemError = Class.new(Error)
44
- StandardError = Class.new(Error)
45
- SyntaxError = Class.new(Error)
46
- StackLevelError = Class.new(Error)
47
- TaintedError = Class.new(Error)
48
- MemoryError = Class.new(Error)
49
- ZeroDivisionError = Class.new(Error)
50
- FloatDomainError = Class.new(Error)
51
- UndefinedVariable = Class.new(Error)
43
+ ArgumentError = Class.new(Error)
44
+ ContextError = Class.new(Error)
45
+ FileSystemError = Class.new(Error)
46
+ StandardError = Class.new(Error)
47
+ SyntaxError = Class.new(Error)
48
+ StackLevelError = Class.new(Error)
49
+ MemoryError = Class.new(Error)
50
+ ZeroDivisionError = Class.new(Error)
51
+ FloatDomainError = Class.new(Error)
52
+ UndefinedVariable = Class.new(Error)
52
53
  UndefinedDropMethod = Class.new(Error)
53
- UndefinedFilter = Class.new(Error)
54
+ UndefinedFilter = Class.new(Error)
54
55
  MethodOverrideError = Class.new(Error)
55
- InternalError = Class.new(Error)
56
+ DisabledError = Class.new(Error)
57
+ InternalError = Class.new(Error)
56
58
  end
@@ -1,45 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Expression
3
- class MethodLiteral
4
- attr_reader :method_name, :to_s
5
-
6
- def initialize(method_name, to_s)
7
- @method_name = method_name
8
- @to_s = to_s
9
- end
10
-
11
- def to_liquid
12
- to_s
13
- end
14
- end
15
-
16
5
  LITERALS = {
17
- nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
18
- 'true'.freeze => true,
19
- 'false'.freeze => false,
20
- 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
21
- 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
6
+ nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
7
+ 'true' => true,
8
+ 'false' => false,
9
+ 'blank' => '',
10
+ 'empty' => ''
22
11
  }.freeze
23
12
 
24
- SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
25
- DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
26
- INTEGERS_REGEX = /\A(-?\d+)\z/
27
- FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
28
- RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
13
+ SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m
14
+ DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
15
+ INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
16
+ FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
17
+
18
+ # Use an atomic group (?>...) to avoid pathological backtracing from
19
+ # malicious input as described in https://github.com/Shopify/liquid/issues/1357
20
+ RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/
29
21
 
30
22
  def self.parse(markup)
31
- if LITERALS.key?(markup)
32
- LITERALS[markup]
23
+ case markup
24
+ when nil
25
+ nil
26
+ when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
27
+ Regexp.last_match(1)
28
+ when INTEGERS_REGEX
29
+ Regexp.last_match(1).to_i
30
+ when RANGES_REGEX
31
+ RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
32
+ when FLOATS_REGEX
33
+ Regexp.last_match(1).to_f
33
34
  else
34
- case markup
35
- when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
36
- $1
37
- when INTEGERS_REGEX
38
- $1.to_i
39
- when RANGES_REGEX
40
- RangeLookup.parse($1, $2)
41
- when FLOATS_REGEX
42
- $1.to_f
35
+ markup = markup.strip
36
+ if LITERALS.key?(markup)
37
+ LITERALS[markup]
43
38
  else
44
39
  VariableLookup.parse(markup)
45
40
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'time'
2
4
  require 'date'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
3
5
  #
@@ -44,8 +46,8 @@ module Liquid
44
46
  class LocalFileSystem
45
47
  attr_accessor :root
46
48
 
47
- def initialize(root, pattern = "_%s.liquid".freeze)
48
- @root = root
49
+ def initialize(root, pattern = "_%s.liquid")
50
+ @root = root
49
51
  @pattern = pattern
50
52
  end
51
53
 
@@ -57,9 +59,9 @@ module Liquid
57
59
  end
58
60
 
59
61
  def full_path(template_path)
60
- raise FileSystemError, "Illegal template name '#{template_path}'" unless template_path =~ /\A[^.\/][a-zA-Z0-9_\/]+\z/
62
+ raise FileSystemError, "Illegal template name '#{template_path}'" unless %r{\A[^./][a-zA-Z0-9_/]+\z}.match?(template_path)
61
63
 
62
- full_path = if template_path.include?('/'.freeze)
64
+ full_path = if template_path.include?('/')
63
65
  File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
64
66
  else
65
67
  File.join(root, @pattern % template_path)
@@ -1,13 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class ForloopDrop < Drop
3
5
  def initialize(name, length, parentloop)
4
- @name = name
5
- @length = length
6
+ @name = name
7
+ @length = length
6
8
  @parentloop = parentloop
7
- @index = 0
9
+ @index = 0
8
10
  end
9
11
 
10
- attr_reader :name, :length, :parentloop
12
+ attr_reader :length, :parentloop
13
+
14
+ def name
15
+ Usage.increment('forloop_drop_name')
16
+ @name
17
+ end
11
18
 
12
19
  def index
13
20
  @index + 1
data/lib/liquid/i18n.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Liquid
@@ -26,13 +28,13 @@ module Liquid
26
28
  def interpolate(name, vars)
27
29
  name.gsub(/%\{(\w+)\}/) do
28
30
  # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
29
- (vars[$1.to_sym]).to_s
31
+ (vars[Regexp.last_match(1).to_sym]).to_s
30
32
  end
31
33
  end
32
34
 
33
35
  def deep_fetch_translation(name)
34
- name.split('.'.freeze).reduce(locale) do |level, cur|
35
- level[cur] or raise TranslationError, "Translation for #{name} does not exist in locale #{path}"
36
+ name.split('.').reduce(locale) do |level, cur|
37
+ level[cur] || raise(TranslationError, "Translation for #{name} does not exist in locale #{path}")
36
38
  end
37
39
  end
38
40
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # An interrupt is any command that breaks processing of a block (ex: a for loop).
3
5
  class Interrupt
4
6
  attr_reader :message
5
7
 
6
8
  def initialize(message = nil)
7
- @message = message || "interrupt".freeze
9
+ @message = message || "interrupt"
8
10
  end
9
11
  end
10
12
 
data/lib/liquid/lexer.rb CHANGED
@@ -1,24 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "strscan"
2
4
  module Liquid
3
5
  class Lexer
4
6
  SPECIALS = {
5
- '|'.freeze => :pipe,
6
- '.'.freeze => :dot,
7
- ':'.freeze => :colon,
8
- ','.freeze => :comma,
9
- '['.freeze => :open_square,
10
- ']'.freeze => :close_square,
11
- '('.freeze => :open_round,
12
- ')'.freeze => :close_round,
13
- '?'.freeze => :question,
14
- '-'.freeze => :dash
7
+ '|' => :pipe,
8
+ '.' => :dot,
9
+ ':' => :colon,
10
+ ',' => :comma,
11
+ '[' => :open_square,
12
+ ']' => :close_square,
13
+ '(' => :open_round,
14
+ ')' => :close_round,
15
+ '?' => :question,
16
+ '-' => :dash,
15
17
  }.freeze
16
- IDENTIFIER = /[a-zA-Z_][\w-]*\??/
18
+ IDENTIFIER = /[a-zA-Z_][\w-]*\??/
17
19
  SINGLE_STRING_LITERAL = /'[^\']*'/
18
20
  DOUBLE_STRING_LITERAL = /"[^\"]*"/
19
- NUMBER_LITERAL = /-?\d+(\.\d+)?/
20
- DOTDOT = /\.\./
21
- COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
21
+ NUMBER_LITERAL = /-?\d+(\.\d+)?/
22
+ DOTDOT = /\.\./
23
+ COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
22
24
  WHITESPACE_OR_NOTHING = /\s*/
23
25
 
24
26
  def initialize(input)
@@ -31,16 +33,21 @@ module Liquid
31
33
  until @ss.eos?
32
34
  @ss.skip(WHITESPACE_OR_NOTHING)
33
35
  break if @ss.eos?
34
- tok = case
35
- when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
36
- when t = @ss.scan(SINGLE_STRING_LITERAL) then [:string, t]
37
- when t = @ss.scan(DOUBLE_STRING_LITERAL) then [:string, t]
38
- when t = @ss.scan(NUMBER_LITERAL) then [:number, t]
39
- when t = @ss.scan(IDENTIFIER) then [:id, t]
40
- when t = @ss.scan(DOTDOT) then [:dotdot, t]
36
+ tok = if (t = @ss.scan(COMPARISON_OPERATOR))
37
+ [:comparison, t]
38
+ elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
39
+ [:string, t]
40
+ elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
41
+ [:string, t]
42
+ elsif (t = @ss.scan(NUMBER_LITERAL))
43
+ [:number, t]
44
+ elsif (t = @ss.scan(IDENTIFIER))
45
+ [:id, t]
46
+ elsif (t = @ss.scan(DOTDOT))
47
+ [:dotdot, t]
41
48
  else
42
- c = @ss.getch
43
- if s = SPECIALS[c]
49
+ c = @ss.getch
50
+ if (s = SPECIALS[c])
44
51
  [s, c]
45
52
  else
46
53
  raise SyntaxError, "Unexpected character #{c}"
@@ -20,7 +20,9 @@
20
20
  tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
21
21
  variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
22
22
  tag_never_closed: "'%{block_name}' tag was never closed"
23
- meta_syntax_error: "Liquid syntax error: #{e.message}"
24
23
  table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
24
+ render: "Syntax error in tag 'render' - Template name must be a quoted string"
25
25
  argument:
26
26
  include: "Argument error in tag 'include' - Illegal template name"
27
+ disabled:
28
+ tag: "usage is not allowed in this context"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class ParseContext
3
5
  attr_accessor :locale, :line_number, :trim_whitespace, :depth
@@ -5,9 +7,11 @@ module Liquid
5
7
 
6
8
  def initialize(options = {})
7
9
  @template_options = options ? options.dup : {}
8
- @locale = @template_options[:locale] ||= I18n.new
10
+
11
+ @locale = @template_options[:locale] ||= I18n.new
9
12
  @warnings = []
10
- self.depth = 0
13
+
14
+ self.depth = 0
11
15
  self.partial = false
12
16
  end
13
17
 
@@ -15,11 +19,23 @@ module Liquid
15
19
  @options[option_key]
16
20
  end
17
21
 
22
+ def new_block_body
23
+ Liquid::BlockBody.new
24
+ end
25
+
26
+ def new_tokenizer(markup, start_line_number: nil, for_liquid_tag: false)
27
+ Tokenizer.new(markup, line_number: start_line_number, for_liquid_tag: for_liquid_tag)
28
+ end
29
+
30
+ def parse_expression(markup)
31
+ Expression.parse(markup)
32
+ end
33
+
18
34
  def partial=(value)
19
35
  @partial = value
20
36
  @options = value ? partial_options : @template_options
37
+
21
38
  @error_mode = @options[:error_mode] || Template.error_mode
22
- value
23
39
  end
24
40
 
25
41
  def partial_options
@@ -28,7 +44,7 @@ module Liquid
28
44
  if dont_pass == true
29
45
  { locale: locale }
30
46
  elsif dont_pass.is_a?(Array)
31
- @template_options.reject { |k, v| dont_pass.include?(k) }
47
+ @template_options.reject { |k, _v| dont_pass.include?(k) }
32
48
  else
33
49
  @template_options
34
50
  end
@@ -11,7 +11,7 @@ module Liquid
11
11
  end
12
12
 
13
13
  def initialize(node, callbacks)
14
- @node = node
14
+ @node = node
15
15
  @callbacks = callbacks
16
16
  end
17
17
 
@@ -28,7 +28,7 @@ module Liquid
28
28
  item, new_context = @callbacks[node.class].call(node, context)
29
29
  [
30
30
  item,
31
- ParseTreeVisitor.for(node, @callbacks).visit(new_context || context)
31
+ ParseTreeVisitor.for(node, @callbacks).visit(new_context || context),
32
32
  ]
33
33
  end
34
34
  end
data/lib/liquid/parser.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Parser
3
5
  def initialize(input)
4
- l = Lexer.new(input)
6
+ l = Lexer.new(input)
5
7
  @tokens = l.tokenize
6
- @p = 0 # pointer to current location
8
+ @p = 0 # pointer to current location
7
9
  end
8
10
 
9
11
  def jump(point)
@@ -46,11 +48,18 @@ module Liquid
46
48
 
47
49
  def expression
48
50
  token = @tokens[@p]
49
- if token[0] == :id
50
- variable_signature
51
- elsif [:string, :number].include? token[0]
51
+ case token[0]
52
+ when :id
53
+ str = consume
54
+ str << variable_lookups
55
+ when :open_square
56
+ str = consume
57
+ str << expression
58
+ str << consume(:close_square)
59
+ str << variable_lookups
60
+ when :string, :number
52
61
  consume
53
- elsif token.first == :open_round
62
+ when :open_round
54
63
  consume
55
64
  first = expression
56
65
  consume(:dotdot)
@@ -63,26 +72,29 @@ module Liquid
63
72
  end
64
73
 
65
74
  def argument
66
- str = ""
75
+ str = +""
67
76
  # might be a keyword argument (identifier: expression)
68
77
  if look(:id) && look(:colon, 1)
69
- str << consume << consume << ' '.freeze
78
+ str << consume << consume << ' '
70
79
  end
71
80
 
72
81
  str << expression
73
82
  str
74
83
  end
75
84
 
76
- def variable_signature
77
- str = consume(:id)
78
- while look(:open_square)
79
- str << consume
80
- str << expression
81
- str << consume(:close_square)
82
- end
83
- if look(:dot)
84
- str << consume
85
- str << variable_signature
85
+ def variable_lookups
86
+ str = +""
87
+ loop do
88
+ if look(:open_square)
89
+ str << consume
90
+ str << expression
91
+ str << consume(:close_square)
92
+ elsif look(:dot)
93
+ str << consume
94
+ str << consume(:id)
95
+ else
96
+ break
97
+ end
86
98
  end
87
99
  str
88
100
  end