liquid2 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42973b8aae08cf4321586ac4cdaf39ecab29e0a2a4b7f26aed278291e41b7645
4
- data.tar.gz: 9c2bca82b7f589cfdccd4a2a5c091ac3cd32613e0c40354b6313d23a6c35bbec
3
+ metadata.gz: 474a8dc5c84a97344741bc74d459cc904f1da7025af450c834fb8d416eb98518
4
+ data.tar.gz: 2dda5436fd261f0208123baa7f7f917f2406531e0bf295db085528a8fa7a5baa
5
5
  SHA512:
6
- metadata.gz: fbb3917b6b68ba37aaffbf158aac61d85a577ddf244b12fdad9eaa99e5ea54a0f94b255b3d21381514bb42bbb7c3543c247b7bba38255e95dacc195aedf2f10a
7
- data.tar.gz: a61fd0b11ff0d4ed92bdcd1b8eca60b5997f6881caad131132256f561a0c039cdc33e758601b7dd9da0e02534f53c21a38da6c09e38bc947a4a93834a0413210
6
+ metadata.gz: 489dd5089b45f07779de0ee25ee4a8fa5b028a8721bf1423a6ca6884ebde6647d36e9e9450e650c2816c1a63a5b6f1554d8e2d8fa7712c106792a3dbeefe0205
7
+ data.tar.gz: 7b5e904527625b34d0e177168abbd00198f393b303b9504ee68a37cda3a2f1ef7a407333b15dec96907271610ca715f0e08f49c695466f8f7ddff22a1056b6d4
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.4.0] - 25-08-11
2
+
3
+ - Fixed a bug where the parser would raise a `Liquid2::LiquidSyntaxError` if environment arguments `markup_out_end` and `markup_tag_end` where identical. See [#23](https://github.com/jg-rp/ruby-liquid2/issues/23).
4
+ - Added `Liquid2::Environment.persistent_namespaces`. It is an array of symbols indicating which namespaces from `Liquid2::RenderContext.tag_namespaces` should be preserved when calling `Liquid2::RenderContext#copy`. This is important for some tags - like `{% extends %}` - that need to share state with partial templates rendered with `{% render %}`.
5
+ - Added the `auto_trim` argument to `Liquid2::Environment`. `auto_trim` can be `'-'`, `'~'` or `nil` (the default). When not `nil`, it sets the automatic whitespace trimming applied to the left side of template text when no explicit whitespace control is given. `+` is also available as whitespace control in tags, outputs statements and comments. A `+` will ensure no trimming is applied, even if `auto_trim` is set.
6
+
7
+ ## [0.3.1] - 25-06-24
8
+
9
+ - Added support for custom markup delimiters. See [#16](https://github.com/jg-rp/ruby-liquid2/pull/16).
10
+ - Added the `range` filter. `range` is an array slicing filter that takes optional start and end indexes, and an optional step argument, any of which can be negative. See [#18](https://github.com/jg-rp/ruby-liquid2/pull/18).
11
+
1
12
  ## [0.3.0] - 25-05-29
2
13
 
3
14
  - Fixed static analysis of lambda expressions (arrow functions). Previously we were not including lambda parameters in the scope of the expression. See [#12](https://github.com/jg-rp/ruby-liquid2/issues/12).
data/README.md CHANGED
@@ -36,7 +36,7 @@ Liquid templates for Ruby, with some extra features.
36
36
  Add `'liquid2'` to your Gemfile:
37
37
 
38
38
  ```
39
- gem 'liquid2', '~> 0.3.0'
39
+ gem 'liquid2', '~> 0.4.0'
40
40
  ```
41
41
 
42
42
  Or
@@ -231,7 +231,13 @@ Integer and float literals can use scientific notation, like `1.2e3` or `1e-2`.
231
231
 
232
232
  Liquid2 includes implementations of `{% extends %}` and `{% block %}` for template inheritance, `{% with %}` for block scoped variables and `{% macro %}` and `{% call %}` for defining parameterized blocks.
233
233
 
234
- There's also built-in implementations of `sort_numeric` and `json` filters.
234
+ The following filters are included in Liquid2's default environment:
235
+
236
+ - `sort_numeric` - Sorts array elements by runs of digits found in their string representation.
237
+ - `json` - Outputs objects serialized in JSON format.
238
+ - `range`- An alternative to the standard `slice` filter that takes optional start and stop indexes, and an optional step, all of which can be negative.
239
+
240
+ See [Tags and filters](#tags-and-filters) for how to add, remove or alias tags and/or filters from your own Liquid2 environment.
235
241
 
236
242
  ## API
237
243
 
@@ -219,8 +219,10 @@ module Liquid2
219
219
  loop_carry: loop_carry,
220
220
  local_namespace_carry: @assign_score)
221
221
 
222
- # XXX: bit of a hack
223
- context.tag_namespace[:extends] = @tag_namespace[:extends]
222
+ @env.persistent_namespaces.each do |ns|
223
+ context.tag_namespace[ns] = @tag_namespace[ns] if @tag_namespace[ns]
224
+ end
225
+
224
226
  context
225
227
  end
226
228
 
@@ -38,15 +38,27 @@ require_relative "nodes/tags/with"
38
38
  module Liquid2
39
39
  # Template parsing and rendering configuration.
40
40
  #
41
- # A Liquid::Environment is where you might register custom tags and filters,
41
+ # A Liquid2::Environment is where you might register custom tags and filters,
42
42
  # or store global context data that should be available to all templates.
43
43
  #
44
44
  # `Liquid2.parse(source)` is equivalent to `Liquid2::Environment.new.parse(source)`.
45
45
  class Environment
46
46
  attr_reader :tags, :local_namespace_limit, :context_depth_limit, :loop_iteration_limit,
47
47
  :output_stream_limit, :filters, :suppress_blank_control_flow_blocks,
48
- :shorthand_indexes, :falsy_undefined, :arithmetic_operators
48
+ :shorthand_indexes, :falsy_undefined, :arithmetic_operators, :markup_comment_prefix,
49
+ :markup_comment_suffix, :markup_out_end, :markup_out_start, :markup_tag_end,
50
+ :markup_tag_start, :re_tag_name, :re_word, :re_int, :re_float,
51
+ :re_double_quote_string_special, :re_single_quote_string_special, :re_markup_start,
52
+ :re_markup_end, :re_markup_end_chars, :re_up_to_markup_start, :re_punctuation,
53
+ :re_up_to_inline_comment_end, :re_up_to_raw_end, :re_block_comment_chunk,
54
+ :re_up_to_doc_end, :re_line_statement_comment, :persistent_namespaces,
55
+ :universal_markup_end
49
56
 
57
+ # @param arithmetic_operators [bool] When `true`, arithmetic operators `+`, `-`, `*`, `/`, `%`
58
+ # and `**` are enabled.
59
+ # @auto_trim ['-' | '~' | nil] Whitespace trimming to apply to the left of text when
60
+ # neither `-` or `~` is given for any tag, output statement or comment. The default is
61
+ # `nil`, which means no automatic whitespace trimming is applied.
50
62
  # @param context_depth_limit [Integer] The maximum number of times a render context can
51
63
  # be extended or copied before a `Liquid2::LiquidResourceLimitError`` is raised.
52
64
  # @param globals [Hash[String, untyped]?] Variables that are available to all templates
@@ -59,8 +71,23 @@ module Liquid2
59
71
  # `Liquid2::LiquidResourceLimitError`` is raised.
60
72
  # @param loop_iteration_limit [Integer?] The maximum number of loop iterations allowed
61
73
  # before a `LiquidResourceLimitError` is raised.
74
+ # @param markup_comment_prefix [String] The string of characters that indicate the start of a
75
+ # Liquid comment. This should include a single trailing `#`. Additional, variable length
76
+ # hashes will be handled by the tokenizer. It is not possible to change comment syntax to not
77
+ # use `#`.
78
+ # @param markup_comment_suffix [String] The string of characters that indicate the end of a
79
+ # Liquid comment, excluding any hashes.
80
+ # @param markup_out_end [String] The string of characters that indicate the end of a Liquid
81
+ # output statement.
82
+ # @param markup_out_start [String] The string of characters that indicate the start of a Liquid
83
+ # output statement.
84
+ # @param markup_tag_end [String] The string of characters that indicate the end of a Liquid tag.
85
+ # @param markup_tag_start [String] The string of characters that indicate the start of a Liquid
86
+ # tag.
62
87
  # @param output_stream_limit [Integer?] The maximum number of bytes that can be written
63
88
  # to a template's output buffer before a `LiquidResourceLimitError` is raised.
89
+ # @param parser [singleton(Parser)] `Liquid2::Parser` or a subclass of it.
90
+ # @param scanner [singleton(Scanner)] `Liquid2::Scanner` or a subclass of it.
64
91
  # @param shorthand_indexes [bool] When `true`, allow shorthand dotted array indexes as
65
92
  # well as bracketed indexes in variable paths. Defaults to `false`.
66
93
  # @param suppress_blank_control_flow_blocks [bool] When `true`, suppress blank control
@@ -70,15 +97,24 @@ module Liquid2
70
97
  def initialize(
71
98
  arithmetic_operators: false,
72
99
  context_depth_limit: 30,
100
+ auto_trim: nil,
101
+ falsy_undefined: true,
73
102
  globals: nil,
74
103
  loader: nil,
75
104
  local_namespace_limit: nil,
76
105
  loop_iteration_limit: nil,
106
+ markup_comment_prefix: "{#",
107
+ markup_comment_suffix: "}",
108
+ markup_out_end: "}}",
109
+ markup_out_start: "{{",
110
+ markup_tag_end: "%}",
111
+ markup_tag_start: "{%",
77
112
  output_stream_limit: nil,
113
+ parser: Parser,
114
+ scanner: Scanner,
78
115
  shorthand_indexes: false,
79
116
  suppress_blank_control_flow_blocks: true,
80
- undefined: Undefined,
81
- falsy_undefined: true
117
+ undefined: Undefined
82
118
  )
83
119
  # A mapping of tag names to objects responding to `parse(token, parser)`.
84
120
  @tags = {}
@@ -88,6 +124,10 @@ module Liquid2
88
124
  # keyword argument.
89
125
  @filters = {}
90
126
 
127
+ # An array of symbols indicating which namespaces from RenderContext.tag_namespaces
128
+ # should be preserved when using RenderContext#copy.
129
+ @persistent_namespaces = [:extends]
130
+
91
131
  # When `true`, arithmetic operators `+`, `-`, `*`, `/`, `%` and `**` are enabled.
92
132
  # Defaults to `false`.
93
133
  @arithmetic_operators = arithmetic_operators
@@ -96,6 +136,11 @@ module Liquid2
96
136
  # a Liquid2::LiquidResourceLimitError is raised.
97
137
  @context_depth_limit = context_depth_limit
98
138
 
139
+ # The default whitespace trimming applied to the left of text content when neither
140
+ # `-` or `~` is specified. Defaults to `nil`, which means no automatic whitespace
141
+ # trimming.
142
+ @auto_trim = auto_trim
143
+
99
144
  # Variables that are available to all templates rendered from this environment.
100
145
  @globals = globals
101
146
 
@@ -116,9 +161,17 @@ module Liquid2
116
161
  # before a `LiquidResourceLimitError` is raised.
117
162
  @output_stream_limit = output_stream_limit
118
163
 
164
+ # Liquid2::Scanner or a subclass of it. This is used to tokenize Liquid source
165
+ # text before parsing it.
166
+ @scanner = scanner
167
+
168
+ # Liquid2::Parser or a subclass of it. The parser takes tokens from the scanner
169
+ # and produces an abstract syntax tree.
170
+ @parser = parser
171
+
119
172
  # We reuse the same string scanner when parsing templates for improved performance.
120
173
  # TODO: Is this going to cause issues in multi threaded environments?
121
- @scanner = StringScanner.new("")
174
+ @string_scanner = StringScanner.new("")
122
175
 
123
176
  # When `true`, allow shorthand dotted array indexes as well as bracketed indexes
124
177
  # in variable paths. Defaults to `false`.
@@ -136,6 +189,35 @@ module Liquid2
136
189
  # raise an error when tested for truthiness.
137
190
  @falsy_undefined = falsy_undefined
138
191
 
192
+ # The string of characters that indicate the start of a Liquid output statement.
193
+ @markup_out_start = markup_out_start
194
+
195
+ # The string of characters that indicate the end of a Liquid output statement.
196
+ @markup_out_end = markup_out_end
197
+
198
+ # The string of characters that indicate the start of a Liquid tag.
199
+ @markup_tag_start = markup_tag_start
200
+
201
+ # The string of characters that indicate the end of a Liquid tag.
202
+ @markup_tag_end = markup_tag_end
203
+
204
+ # Indicates if tag and output end delimiters are identical. This is used by the
205
+ # parser when parsing output statements.
206
+ @universal_markup_end = markup_tag_end == markup_out_end
207
+
208
+ # The string of characters that indicate the start of a Liquid comment. This should
209
+ # include a single trailing `#`. Additional, variable length hashes will be handled
210
+ # by the tokenizer. It is not possible to change comment syntax to not use `#`.
211
+ @markup_comment_prefix = markup_comment_prefix
212
+
213
+ # The string of characters that indicate the end of a Liquid comment, excluding any
214
+ # hashes.
215
+ @markup_comment_suffix = markup_comment_suffix
216
+
217
+ # You might need to override `setup_scanner` if you've specified custom markup
218
+ # delimiters and they conflict with standard punctuation.
219
+ setup_scanner
220
+
139
221
  # Override `setup_tags_and_filters` in environment subclasses to configure custom
140
222
  # tags and/or filters.
141
223
  setup_tags_and_filters
@@ -145,11 +227,13 @@ module Liquid2
145
227
  # @param source [String] template source text.
146
228
  # @return [Template]
147
229
  def parse(source, name: "", path: nil, up_to_date: nil, globals: nil, overlay: nil)
148
- Template.new(self,
149
- source,
150
- Parser.parse(self, source, scanner: @scanner),
151
- name: name, path: path, up_to_date: up_to_date,
152
- globals: make_globals(globals), overlay: overlay)
230
+ Template.new(
231
+ self,
232
+ source,
233
+ @parser.new(self, @scanner.tokenize(self, source, @string_scanner), source.length).parse,
234
+ name: name, path: path, up_to_date: up_to_date,
235
+ globals: make_globals(globals), overlay: overlay
236
+ )
153
237
  rescue LiquidError => e
154
238
  e.source = source unless e.source
155
239
  e.template_name = name unless e.template_name || name.empty?
@@ -262,6 +346,7 @@ module Liquid2
262
346
  register_filter("newline_to_br", Liquid2::Filters.method(:newline_to_br))
263
347
  register_filter("plus", Liquid2::Filters.method(:plus))
264
348
  register_filter("prepend", Liquid2::Filters.method(:prepend))
349
+ register_filter("range", Liquid2::Filters.method(:better_slice))
265
350
  register_filter("reject", Liquid2::Filters.method(:reject))
266
351
  register_filter("remove_first", Liquid2::Filters.method(:remove_first))
267
352
  register_filter("remove_last", Liquid2::Filters.method(:remove_last))
@@ -292,13 +377,58 @@ module Liquid2
292
377
  register_filter("where", Liquid2::Filters.method(:where))
293
378
  end
294
379
 
380
+ # Compile regular expressions for use by the tokenizer attached to this environment.
381
+ def setup_scanner
382
+ # A regex pattern matching Liquid tag names. Should include `#` for inline comments.
383
+ @re_tag_name = /(?:[a-z][a-z_0-9]*|#)/
384
+
385
+ # A regex pattern matching keywords and/or variable/path names. Replace this if
386
+ # you want to disable Unicode characters in identifiers, for example.
387
+ @re_word = /[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*/
388
+
389
+ # Patterns matching literal integers and floats, possibly in scientific notation.
390
+ # You could simplify these to disable scientific notation.
391
+ @re_int = /-?\d+(?:[eE]\+?\d+)?/
392
+ @re_float = /((?:-?\d+\.\d+(?:[eE][+-]?\d+)?)|(-?\d+[eE]-\d+))/
393
+
394
+ # Patterns matching escape sequences, interpolation and end of string in string literals.
395
+ # You could remove `\$` from these to disable string interpolation.
396
+ @re_double_quote_string_special = /[\\"\$]/
397
+ @re_single_quote_string_special = /[\\'\$]/
398
+
399
+ # rubocop: disable Layout/LineLength
400
+
401
+ # A regex pattern matching the start of some Liquid markup. Could be the start of an
402
+ # output statement, tag or comment. Traditionally `{{`, `{%` and `{#`, respectively.
403
+ @re_markup_start = /#{Regexp.escape(@markup_out_start)}|#{Regexp.escape(@markup_tag_start)}|#{Regexp.escape(@markup_comment_prefix)}/
404
+
405
+ # A regex pattern matching the end of some Liquid markup. Could be the end of
406
+ # an output statement or tag. Traditionally `}}`, `%}`, respectively.
407
+ @re_markup_end = /#{Regexp.escape(@markup_out_end)}|#{Regexp.escape(@markup_tag_end)}/
408
+
409
+ # A regex pattern matching any one of the possible characters ending some Liquid
410
+ # markup. This is used to detect incomplete and malformed markup and provide
411
+ # helpful error messages.
412
+ @re_markup_end_chars = /[#{Regexp.escape((@markup_out_end + @markup_tag_end).each_char.uniq.join)}]/
413
+
414
+ @re_up_to_markup_start = /(?=#{Regexp.escape(@markup_out_start)}|#{Regexp.escape(@markup_tag_start)}|#{Regexp.escape(@markup_comment_prefix)})/
415
+ @re_punctuation = %r{(?!#{@re_markup_end})(\?|\[|\]|\|{1,2}|\.{1,2}|,|:|\(|\)|[<>=!]+|[+\-%*/]+(?!#{@re_markup_end_chars}))}
416
+ @re_up_to_inline_comment_end = /(?=([+\-~])?#{Regexp.escape(@markup_tag_end)})/
417
+ @re_up_to_raw_end = /(?=(#{Regexp.escape(@markup_tag_start)}[+\-~]?\s*endraw\s*[+\-~]?#{Regexp.escape(@markup_tag_end)}))/
418
+ @re_block_comment_chunk = /(#{Regexp.escape(@markup_tag_start)}[+\-~]?\s*(comment|raw|endcomment|endraw)\s*[+\-~]?#{Regexp.escape(@markup_tag_end)})/
419
+ @re_up_to_doc_end = /(?=(#{Regexp.escape(@markup_tag_start)}[+\-~]?\s*enddoc\s*[+\-~]?#{Regexp.escape(@markup_tag_end)}))/
420
+ @re_line_statement_comment = /(?=([\r\n]+|-?#{Regexp.escape(@markup_tag_end)}))/
421
+
422
+ # rubocop: enable Layout/LineLength
423
+ end
424
+
295
425
  def undefined(name, node: nil)
296
426
  @undefined.new(name, node: node)
297
427
  end
298
428
 
299
429
  # Trim _text_.
300
430
  def trim(text, left_trim, right_trim)
301
- case left_trim
431
+ case left_trim || @auto_trim
302
432
  when "-"
303
433
  text.lstrip!
304
434
  when "~"
@@ -13,5 +13,45 @@ module Liquid2
13
13
  Liquid2.to_s(left).slice(to_integer(start), to_integer(length)) || ""
14
14
  end
15
15
  end
16
+
17
+ def self.better_slice(
18
+ left,
19
+ start_ = :undefined, stop_ = :undefined, step_ = :undefined,
20
+ start: :undefined, stop: :undefined, step: :undefined
21
+ )
22
+ # Give priority to keyword arguments, default to nil if neither are given.
23
+ start = start_ == :undefined ? nil : start_ if start == :undefined
24
+ stop = stop_ == :undefined ? nil : stop_ if stop == :undefined
25
+ step = step_ == :undefined ? nil : step_ if step == :undefined
26
+
27
+ step = to_integer(step || 1)
28
+ length = left.length
29
+ return [] if length.zero? || step.zero?
30
+
31
+ start = to_integer(start) unless start.nil?
32
+ stop = to_integer(stop) unless stop.nil?
33
+
34
+ normalized_start = if start.nil?
35
+ step.negative? ? length - 1 : 0
36
+ elsif start&.negative?
37
+ [length + start, 0].max
38
+ else
39
+ [start, length - 1].min
40
+ end
41
+
42
+ normalized_stop = if stop.nil?
43
+ step.negative? ? -1 : length
44
+ elsif stop&.negative?
45
+ [length + stop, -1].max
46
+ else
47
+ [stop, length].min
48
+ end
49
+
50
+ # This does not work with Ruby 3.1
51
+ # left[(normalized_start...normalized_stop).step(step)]
52
+ #
53
+ # But this does.
54
+ (normalized_start...normalized_stop).step(step).map { |i| left[i] }
55
+ end
16
56
  end
17
57
  end
@@ -25,11 +25,12 @@ module Liquid2
25
25
  # Liquid template parser.
26
26
  class Parser
27
27
  # Parse Liquid template text into a syntax tree.
28
+ # @param env [Environment]
28
29
  # @param source [String]
29
30
  # @return [Array[Node | String]]
30
31
  def self.parse(env, source, scanner: nil)
31
32
  new(env,
32
- Liquid2::Scanner.tokenize(source, scanner || StringScanner.new("")),
33
+ Liquid2::Scanner.tokenize(env, source, scanner || StringScanner.new("")),
33
34
  source.length).parse
34
35
  end
35
36
 
@@ -42,6 +43,10 @@ module Liquid2
42
43
  @pos = 0
43
44
  @eof = [:token_eof, nil, length - 1]
44
45
  @whitespace_carry = nil
46
+
47
+ # If both tags and output statements share the same end delimiter, we expect
48
+ # `:token_tag_end` to close an output statement as the scanner scans for tags first.
49
+ @output_end = @env.universal_markup_end ? :token_tag_end : :token_output_end
45
50
  end
46
51
 
47
52
  # Return the current token without advancing the pointer.
@@ -690,7 +695,7 @@ module Liquid2
690
695
  def parse_output
691
696
  expr = parse_filtered_expression
692
697
  carry_whitespace_control
693
- eat(:token_output_end)
698
+ eat(@output_end)
694
699
  Output.new(expr.token, expr)
695
700
  end
696
701
 
@@ -824,15 +829,7 @@ module Liquid2
824
829
  return parse_partial_arrow_function(expr)
825
830
  end
826
831
 
827
- unless TERMINATE_GROUPED_EXPRESSION.member?(kind)
828
- unless BINARY_OPERATORS.member?(kind)
829
- raise LiquidSyntaxError.new("expected an infix operator, found #{kind}", current)
830
- end
831
-
832
- expr = parse_infix_expression(expr)
833
- end
834
-
835
- eat(:token_rparen)
832
+ eat(:token_rparen, "unbalanced parentheses")
836
833
  expr
837
834
  end
838
835
 
@@ -12,14 +12,6 @@ module Liquid2
12
12
  class Scanner
13
13
  attr_reader :tokens
14
14
 
15
- RE_LINE_SPACE = /[ \t]+/
16
- RE_WORD = /[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*/
17
- RE_INT = /-?\d+(?:[eE]\+?\d+)?/
18
- RE_FLOAT = /((?:-?\d+\.\d+(?:[eE][+-]?\d+)?)|(-?\d+[eE]-\d+))/
19
- RE_PUNCTUATION = %r{\?|\[|\]|\|{1,2}|\.{1,2}|,|:|\(|\)|[<>=!]+|[+\-%*/]+(?![\}%])}
20
- RE_SINGLE_QUOTE_STRING_SPECIAL = /[\\'\$]/
21
- RE_DOUBLE_QUOTE_STRING_SPECIAL = /[\\"\$]/
22
-
23
15
  # Keywords and symbols that get their own token kind.
24
16
  TOKEN_MAP = {
25
17
  "true" => :token_true,
@@ -68,15 +60,16 @@ module Liquid2
68
60
  "**" => :token_pow
69
61
  }.freeze
70
62
 
71
- def self.tokenize(source, scanner)
72
- lexer = new(source, scanner)
63
+ def self.tokenize(env, source, scanner)
64
+ lexer = new(env, source, scanner)
73
65
  lexer.run
74
66
  lexer.tokens
75
67
  end
76
68
 
69
+ # @param env [Environment]
77
70
  # @param source [String]
78
71
  # @param scanner [StringScanner]
79
- def initialize(source, scanner)
72
+ def initialize(env, source, scanner)
80
73
  @source = source
81
74
  @scanner = scanner
82
75
  @scanner.string = @source
@@ -84,8 +77,33 @@ module Liquid2
84
77
  # A pointer to the start of the current token.
85
78
  @start = 0
86
79
 
87
- # Tokens are arrays of (kind, value, start index)
80
+ # Tokens are arrays of (kind, value, start index).
81
+ # Sometimes we set value to `nil` when the symbol is unambiguous.
88
82
  @tokens = [] # : Array[[Symbol, String?, Integer]]
83
+
84
+ @s_out_start = env.markup_out_start
85
+ @s_out_end = env.markup_out_end
86
+ @s_tag_start = env.markup_tag_start
87
+ @s_tag_end = env.markup_tag_end
88
+ @s_comment_prefix = env.markup_comment_prefix
89
+ @s_comment_suffix = env.markup_comment_suffix
90
+
91
+ @re_tag_name = env.re_tag_name
92
+ @re_word = env.re_word
93
+ @re_int = env.re_int
94
+ @re_float = env.re_float
95
+ @re_double_quote_string_special = env.re_double_quote_string_special
96
+ @re_single_quote_string_special = env.re_single_quote_string_special
97
+ @re_markup_start = env.re_markup_start
98
+ @re_markup_end = env.re_markup_end
99
+ @re_markup_end_chars = env.re_markup_end_chars
100
+ @re_up_to_markup_start = env.re_up_to_markup_start
101
+ @re_punctuation = env.re_punctuation
102
+ @re_up_to_inline_comment_end = env.re_up_to_inline_comment_end
103
+ @re_up_to_raw_end = env.re_up_to_raw_end
104
+ @re_block_comment_chunk = env.re_block_comment_chunk
105
+ @re_up_to_doc_end = env.re_up_to_doc_end
106
+ @re_line_statement_comment = env.re_line_statement_comment
89
107
  end
90
108
 
91
109
  def run
@@ -108,14 +126,13 @@ module Liquid2
108
126
  end
109
127
 
110
128
  def skip_line_trivia
111
- @start = @scanner.pos if @scanner.skip(RE_LINE_SPACE)
129
+ @start = @scanner.pos if @scanner.skip(/[ \t]+/)
112
130
  end
113
131
 
114
132
  def accept_whitespace_control
115
133
  ch = @scanner.peek(1)
116
134
 
117
- case ch
118
- when "-", "+", "~"
135
+ if ch == "-" || ch == "+" || ch == "~" # rubocop: disable Style/MultipleComparison
119
136
  @scanner.pos += 1
120
137
  @tokens << [:token_whitespace_control, ch, @start]
121
138
  @start = @scanner.pos
@@ -126,22 +143,22 @@ module Liquid2
126
143
  end
127
144
 
128
145
  def lex_markup
129
- case @scanner.scan(/\{[\{%#]/)
130
- when "{#"
146
+ case @scanner.scan(@re_markup_start)
147
+ when @s_comment_prefix
131
148
  :lex_comment
132
- when "{{"
149
+ when @s_out_start
133
150
  @tokens << [:token_output_start, nil, @start]
134
151
  @start = @scanner.pos
135
152
  accept_whitespace_control
136
153
  skip_trivia
137
154
  :lex_expression
138
- when "{%"
155
+ when @s_tag_start
139
156
  @tokens << [:token_tag_start, nil, @start]
140
157
  @start = @scanner.pos
141
158
  accept_whitespace_control
142
159
  skip_trivia
143
160
 
144
- if (tag_name = @scanner.scan(/(?:[a-z][a-z_0-9]*|#)/))
161
+ if (tag_name = @scanner.scan(@re_tag_name))
145
162
  @tokens << [:token_tag_name, tag_name, @start]
146
163
  @start = @scanner.pos
147
164
 
@@ -173,8 +190,7 @@ module Liquid2
173
190
  :lex_expression
174
191
  end
175
192
  else
176
- if @scanner.skip_until(/\{[\{%#]/)
177
- @scanner.pos -= 2
193
+ if @scanner.skip_until(@re_up_to_markup_start)
178
194
  @tokens << [:token_other, @source.byteslice(@start...@scanner.pos), @start]
179
195
  @start = @scanner.pos
180
196
  :lex_markup
@@ -192,26 +208,27 @@ module Liquid2
192
208
  def lex_expression
193
209
  loop do
194
210
  skip_trivia
195
- if (value = @scanner.scan(RE_FLOAT))
211
+ if (value = @scanner.scan(@re_float))
196
212
  @tokens << [:token_float, value, @start]
197
213
  @start = @scanner.pos
198
- elsif (value = @scanner.scan(RE_INT))
214
+ elsif (value = @scanner.scan(@re_int))
199
215
  @tokens << [:token_int, value, @start]
200
216
  @start = @scanner.pos
201
- elsif (value = @scanner.scan(RE_PUNCTUATION))
217
+ elsif (value = @scanner.scan(@re_punctuation))
202
218
  @tokens << [TOKEN_MAP[value] || :token_unknown, value, @start]
203
219
  @start = @scanner.pos
204
- elsif (value = @scanner.scan(RE_WORD))
220
+ elsif (value = @scanner.scan(@re_word))
205
221
  @tokens << [TOKEN_MAP[value] || :token_word, value, @start]
206
222
  @start = @scanner.pos
207
223
  else
208
224
  case @scanner.get_byte
209
225
  when "'"
210
226
  @start = @scanner.pos
211
- scan_string("'", :token_single_quote_string, RE_SINGLE_QUOTE_STRING_SPECIAL)
227
+ scan_string("'", :token_single_quote_string, @re_single_quote_string_special)
212
228
  when "\""
213
229
  @start = @scanner.pos
214
- scan_string("\"", :token_double_quote_string, RE_DOUBLE_QUOTE_STRING_SPECIAL)
230
+ scan_string("\"", :token_double_quote_string,
231
+ @re_double_quote_string_special)
215
232
  else
216
233
  @scanner.pos -= 1
217
234
  break
@@ -222,17 +239,17 @@ module Liquid2
222
239
  accept_whitespace_control
223
240
 
224
241
  # Miro benchmarks show no performance gain using scan_byte and peek_byte over scan here.
225
- case @scanner.scan(/[\}%]\}/)
226
- when "}}"
227
- @tokens << [:token_output_end, nil, @start]
228
- when "%}"
242
+ case @scanner.scan(@re_markup_end)
243
+ when @s_tag_end
229
244
  @tokens << [:token_tag_end, nil, @start]
245
+ when @s_out_end
246
+ @tokens << [:token_output_end, nil, @start]
230
247
  else
231
248
  # Unexpected token
232
249
  return nil if @scanner.eos?
233
250
 
234
- if (ch = @scanner.scan(/[\}%]/))
235
- raise LiquidSyntaxError.new("missing \"}\" or \"%\" detected",
251
+ if (ch = @scanner.scan(@re_markup_end_chars))
252
+ raise LiquidSyntaxError.new("missing markup delimiter detected",
236
253
  [:token_unknown, ch, @start])
237
254
  end
238
255
 
@@ -255,8 +272,7 @@ module Liquid2
255
272
 
256
273
  wc = accept_whitespace_control
257
274
 
258
- if @scanner.skip_until(/([+\-~]?)(\#{#{hash_count}}\})/)
259
- @scanner.pos -= @scanner[0]&.length || 0
275
+ if @scanner.skip_until(/(?=([+\-~]?)(\#{#{hash_count}}#{Regexp.escape(@s_comment_suffix)}))/)
260
276
  @tokens << [:token_comment, @source.byteslice(@start...@scanner.pos), @start]
261
277
  @start = @scanner.pos
262
278
 
@@ -282,19 +298,18 @@ module Liquid2
282
298
  end
283
299
 
284
300
  def lex_inside_inline_comment
285
- if @scanner.skip_until(/([+\-~])?%\}/)
286
- @scanner.pos -= @scanner.captures&.first.nil? ? 2 : 3
301
+ if @scanner.skip_until(@re_up_to_inline_comment_end)
287
302
  @tokens << [:token_comment, @source.byteslice(@start...@scanner.pos), @start]
288
303
  @start = @scanner.pos
289
304
  end
290
305
 
291
306
  accept_whitespace_control
292
307
 
293
- case @scanner.scan(/[\}%]\}/)
294
- when "}}"
295
- @tokens << [:token_output_end, nil, @start]
296
- when "%}"
308
+ case @scanner.scan(@re_markup_end)
309
+ when @s_tag_end
297
310
  @tokens << [:token_tag_end, nil, @start]
311
+ when @s_out_end
312
+ @tokens << [:token_output_end, nil, @start]
298
313
  else
299
314
  # Unexpected token
300
315
  return nil if @scanner.eos?
@@ -310,17 +325,16 @@ module Liquid2
310
325
  skip_trivia
311
326
  accept_whitespace_control
312
327
 
313
- case @scanner.scan(/[\}%]\}/)
314
- when "}}"
315
- @tokens << [:token_output_end, nil, @start]
316
- @start = @scanner.pos
317
- when "%}"
328
+ case @scanner.scan(@re_markup_end)
329
+ when @s_tag_end
318
330
  @tokens << [:token_tag_end, nil, @start]
319
331
  @start = @scanner.pos
332
+ when @s_out_end
333
+ @tokens << [:token_output_end, nil, @start]
334
+ @start = @scanner.pos
320
335
  end
321
336
 
322
- if @scanner.skip_until(/(\{%[+\-~]?\s*endraw\s*[+\-~]?%\})/)
323
- @scanner.pos -= @scanner.captures&.first&.length || raise
337
+ if @scanner.skip_until(@re_up_to_raw_end)
324
338
  @tokens << [:token_raw, @source.byteslice(@start...@scanner.pos), @start]
325
339
  @start = @scanner.pos
326
340
  end
@@ -332,22 +346,20 @@ module Liquid2
332
346
  skip_trivia
333
347
  accept_whitespace_control
334
348
 
335
- case @scanner.scan(/[\}%]\}/)
336
- when "}}"
337
- @tokens << [:token_output_end, nil, @start]
338
- @start = @scanner.pos
339
- when "%}"
349
+ case @scanner.scan(@re_markup_end)
350
+ when @s_tag_end
340
351
  @tokens << [:token_tag_end, nil, @start]
341
352
  @start = @scanner.pos
353
+ when @s_out_end
354
+ @tokens << [:token_output_end, nil, @start]
355
+ @start = @scanner.pos
342
356
  end
343
357
 
344
358
  comment_depth = 1
345
359
  raw_depth = 0
346
360
 
347
361
  loop do
348
- unless @scanner.skip_until(/(\{%[+\-~]?\s*(comment|raw|endcomment|endraw)\s*[+\-~]?%\})/)
349
- break
350
- end
362
+ break unless @scanner.skip_until(@re_block_comment_chunk)
351
363
 
352
364
  tag_name = @scanner.captures&.last || raise
353
365
 
@@ -380,17 +392,16 @@ module Liquid2
380
392
  skip_trivia
381
393
  accept_whitespace_control
382
394
 
383
- case @scanner.scan(/[\}%]\}/)
384
- when "}}"
385
- @tokens << [:token_output_end, nil, @start]
386
- @start = @scanner.pos
387
- when "%}"
395
+ case @scanner.scan(@re_markup_end)
396
+ when @s_tag_end
388
397
  @tokens << [:token_tag_end, nil, @start]
389
398
  @start = @scanner.pos
399
+ when @s_out_end
400
+ @tokens << [:token_output_end, nil, @start]
401
+ @start = @scanner.pos
390
402
  end
391
403
 
392
- if @scanner.skip_until(/(\{%[+\-~]?\s*enddoc\s*[+\-~]?%\})/)
393
- @scanner.pos -= @scanner.captures&.first&.length || raise
404
+ if @scanner.skip_until(@re_up_to_doc_end)
394
405
  @tokens << [:token_doc, @source.byteslice(@start...@scanner.pos), @start]
395
406
  @start = @scanner.pos
396
407
  end
@@ -401,21 +412,19 @@ module Liquid2
401
412
  def lex_line_statements
402
413
  skip_trivia # Leading newlines are OK
403
414
 
404
- if (tag_name = @scanner.scan(/(?:[a-z][a-z_0-9]*|#)/))
415
+ if (tag_name = @scanner.scan(@re_tag_name))
405
416
  @tokens << [:token_tag_start, nil, @start]
406
417
  @tokens << [:token_tag_name, tag_name, @start]
407
418
  @start = @scanner.pos
408
419
 
409
- if tag_name == "#" && @scanner.scan_until(/([\r\n]+|-?%\})/)
410
- @scanner.pos -= @scanner.captures&.first&.length || raise
420
+ if tag_name == "#" && @scanner.scan_until(@re_line_statement_comment)
411
421
  @tokens << [:token_comment, @source.byteslice(@start...@scanner.pos), @start]
412
422
  @start = @scanner.pos
413
423
  @tokens << [:token_tag_end, nil, @start]
414
424
  :lex_line_statements
415
425
 
416
- elsif tag_name == "comment" && @scanner.scan_until(/(endcomment)/)
426
+ elsif tag_name == "comment" && @scanner.scan_until(/(?=endcomment)/)
417
427
  @tokens << [:token_tag_end, nil, @start]
418
- @scanner.pos -= @scanner.captures&.first&.length || raise
419
428
  @tokens << [:token_comment, @source.byteslice(@start...@scanner.pos), @start]
420
429
  @start = @scanner.pos
421
430
  :lex_line_statements
@@ -424,13 +433,13 @@ module Liquid2
424
433
  end
425
434
  else
426
435
  accept_whitespace_control
427
- case @scanner.scan(/[\}%]\}/)
428
- when "}}"
429
- @tokens << [:token_output_end, nil, @start]
430
- @start = @scanner.pos
431
- when "%}"
436
+ case @scanner.scan(@re_markup_end)
437
+ when @s_tag_end
432
438
  @tokens << [:token_tag_end, nil, @start]
433
439
  @start = @scanner.pos
440
+ when @s_out_end
441
+ @tokens << [:token_output_end, nil, @start]
442
+ @start = @scanner.pos
434
443
  end
435
444
 
436
445
  :lex_markup
@@ -444,26 +453,26 @@ module Liquid2
444
453
  case @scanner.get_byte
445
454
  when "'"
446
455
  @start = @scanner.pos
447
- scan_string("'", :token_single_quote_string, RE_SINGLE_QUOTE_STRING_SPECIAL)
456
+ scan_string("'", :token_single_quote_string, @re_single_quote_string_special)
448
457
  when "\""
449
458
  @start = @scanner.pos
450
- scan_string("\"", :token_double_quote_string, RE_DOUBLE_QUOTE_STRING_SPECIAL)
459
+ scan_string("\"", :token_double_quote_string, @re_double_quote_string_special)
451
460
  when nil
452
461
  # End of scanner. Unclosed expression or string literal.
453
462
  break
454
463
 
455
464
  else
456
465
  @scanner.pos -= 1
457
- if (value = @scanner.scan(RE_FLOAT))
466
+ if (value = @scanner.scan(@re_float))
458
467
  @tokens << [:token_float, value, @start]
459
468
  @start = @scanner.pos
460
- elsif (value = @scanner.scan(RE_INT))
469
+ elsif (value = @scanner.scan(@re_int))
461
470
  @tokens << [:token_int, value, @start]
462
471
  @start = @scanner.pos
463
- elsif (value = @scanner.scan(RE_PUNCTUATION))
472
+ elsif (value = @scanner.scan(@re_punctuation))
464
473
  @tokens << [TOKEN_MAP[value] || raise, nil, @start]
465
474
  @start = @scanner.pos
466
- elsif (value = @scanner.scan(RE_WORD))
475
+ elsif (value = @scanner.scan(@re_word))
467
476
  @tokens << [TOKEN_MAP[value] || :token_word, value, @start]
468
477
  @start = @scanner.pos
469
478
  elsif @scanner.scan(/(\r?\n)+/)
@@ -475,13 +484,13 @@ module Liquid2
475
484
  # End of the line statement and enclosing `liquid` tag.
476
485
  @tokens << [:token_tag_end, nil, @start]
477
486
  accept_whitespace_control
478
- case @scanner.scan(/[\}%]\}/)
479
- when "}}"
480
- @tokens << [:token_output_end, nil, @start]
481
- @start = @scanner.pos
482
- when "%}"
487
+ case @scanner.scan(@re_markup_end)
488
+ when @s_tag_end
483
489
  @tokens << [:token_tag_end, nil, @start]
484
490
  @start = @scanner.pos
491
+ when @s_out_end
492
+ @tokens << [:token_output_end, nil, @start]
493
+ @start = @scanner.pos
485
494
  end
486
495
 
487
496
  return :lex_markup
@@ -536,10 +545,12 @@ module Liquid2
536
545
  case @scanner.get_byte
537
546
  when "'"
538
547
  @start = @scanner.pos
539
- scan_string("'", :token_single_quote_string, RE_SINGLE_QUOTE_STRING_SPECIAL)
548
+ scan_string("'", :token_single_quote_string,
549
+ @re_single_quote_string_special)
540
550
  when "\""
541
551
  @start = @scanner.pos
542
- scan_string("\"", :token_double_quote_string, RE_DOUBLE_QUOTE_STRING_SPECIAL)
552
+ scan_string("\"", :token_double_quote_string,
553
+ @re_double_quote_string_special)
543
554
  when "}"
544
555
  @tokens << [:token_string_interpol_end, nil, @start]
545
556
  @start = @scanner.pos
@@ -550,16 +561,16 @@ module Liquid2
550
561
  [symbol, nil, start_of_string])
551
562
  else
552
563
  @scanner.pos -= 1
553
- if (value = @scanner.scan(RE_FLOAT))
564
+ if (value = @scanner.scan(@re_float))
554
565
  @tokens << [:token_float, value, @start]
555
566
  @start = @scanner.pos
556
- elsif (value = @scanner.scan(RE_INT))
567
+ elsif (value = @scanner.scan(@re_int))
557
568
  @tokens << [:token_int, value, @start]
558
569
  @start = @scanner.pos
559
- elsif (value = @scanner.scan(RE_PUNCTUATION))
570
+ elsif (value = @scanner.scan(@re_punctuation))
560
571
  @tokens << [TOKEN_MAP[value] || raise, nil, @start]
561
572
  @start = @scanner.pos
562
- elsif (value = @scanner.scan(RE_WORD))
573
+ elsif (value = @scanner.scan(@re_word))
563
574
  @tokens << [TOKEN_MAP[value] || :token_word, value, @start]
564
575
  @start = @scanner.pos
565
576
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid2
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -48,17 +48,11 @@ env = fixture.env
48
48
  source = fixture.templates["index.liquid"]
49
49
  template = env.get_template("index.liquid")
50
50
 
51
- # scanner = StringScanner.new("")
52
-
53
51
  Benchmark.ips do |x|
54
52
  # Configure the number of seconds used during
55
53
  # the warmup phase (default 2) and calculation phase (default 5)
56
54
  x.config(warmup: 2, time: 5)
57
55
 
58
- # x.report("tokenize (#{fixture.name}):") do
59
- # Liquid2::Scanner.tokenize(source, scanner)
60
- # end
61
-
62
56
  x.report("parse (#{fixture.name}):") do
63
57
  env.parse(source)
64
58
  end
data/sig/liquid2.rbs CHANGED
@@ -62,6 +62,8 @@ module Liquid2
62
62
  # keyword argument.
63
63
  @filters: Hash[String, [_Filter, (Integer | nil)]]
64
64
 
65
+ @persistent_namespaces: Array[Symbol]
66
+
65
67
  @local_namespace_limit: Integer?
66
68
 
67
69
  @context_depth_limit: Integer
@@ -82,12 +84,84 @@ module Liquid2
82
84
 
83
85
  @globals: Hash[String, untyped]?
84
86
 
85
- @scanner: StringScanner
87
+ @scanner: singleton(Scanner)
88
+
89
+ @parser: singleton(Parser)
90
+
91
+ @string_scanner: StringScanner
86
92
 
87
93
  @arithmetic_operators: bool
88
94
 
95
+ @auto_trim: '-' | '~' | nil
96
+
97
+ # The string of characters that indicate the start of a Liquid output statement.
98
+ @markup_out_start: String
99
+
100
+ # The string of characters that indicate the end of a Liquid output statement.
101
+ @markup_out_end: String
102
+
103
+ # The string of characters that indicate the start of a Liquid tag.
104
+ @markup_tag_start: String
105
+
106
+ # The string of characters that indicate the end of a Liquid tag.
107
+ @markup_tag_end: String
108
+
109
+ @universal_markup_end: bool
110
+
111
+ # The string of characters that indicate the start of a Liquid comment. This should
112
+ # include a single trailing `#`. Additional, variable length hashes will be handled
113
+ # by the tokenizer. It is not possible to change comment syntax to not use `#`.
114
+ @markup_comment_prefix: String
115
+
116
+ # The string of characters that indicate the end of a Liquid comment, excluding any
117
+ # hashes.
118
+ @markup_comment_suffix: String
119
+
120
+ # A regex pattern matching Liquid tag names. Should include `#` for inline comments.
121
+ @re_tag_name: Regexp
122
+
123
+ @re_word: Regexp
124
+
125
+ @re_int: Regexp
126
+
127
+ @re_float: Regexp
128
+
129
+ @re_double_quote_string_special: Regexp
130
+
131
+ @re_single_quote_string_special: Regexp
132
+
133
+ # A regex pattern matching the start of some Liquid markup. Could be the start of an
134
+ # output statement, tag or comment. Traditionally `{{`, `{%` and `{#`, respectively.
135
+ @re_markup_start: Regexp
136
+
137
+ # A regex pattern matching the end of some Liquid markup. Could be the end of
138
+ # an output statement or tag. Traditionally `}}`, `%}`, respectively.
139
+ # respectively.
140
+ @re_markup_end: Regexp
141
+
142
+ # A regex pattern matching any one of the possible characters ending some Liquid
143
+ # markup. This is used to detect incomplete and malformed markup and provide
144
+ # helpful error messages.
145
+ @re_markup_end_chars: Regexp
146
+
147
+ @re_up_to_markup_start: Regexp
148
+
149
+ @re_punctuation: Regexp
150
+
151
+ @re_up_to_inline_comment_end: Regexp
152
+
153
+ @re_up_to_raw_end: Regexp
154
+
155
+ @re_block_comment_chunk: Regexp
156
+
157
+ @re_up_to_doc_end: Regexp
158
+
159
+ @re_line_statement_comment: Regexp
160
+
89
161
  attr_reader tags: Hash[String, _Tag]
90
162
 
163
+ attr_reader persistent_namespaces: Array[Symbol]
164
+
91
165
  attr_reader local_namespace_limit: Integer?
92
166
 
93
167
  attr_reader context_depth_limit: Integer
@@ -106,7 +180,53 @@ module Liquid2
106
180
 
107
181
  attr_reader arithmetic_operators: bool
108
182
 
109
- def initialize: (?context_depth_limit: ::Integer, ?globals: Hash[String, untyped]?, ?loader: TemplateLoader?, ?local_namespace_limit: Integer?, ?loop_iteration_limit: Integer?, ?output_stream_limit: Integer?, ?shorthand_indexes: bool, ?suppress_blank_control_flow_blocks: bool, ?undefined: singleton(Undefined), ?falsy_undefined: bool) -> void
183
+ attr_reader markup_comment_prefix: String
184
+
185
+ attr_reader markup_comment_suffix: String
186
+
187
+ attr_reader markup_out_end: String
188
+
189
+ attr_reader markup_out_start: String
190
+
191
+ attr_reader markup_tag_end: String
192
+
193
+ attr_reader markup_tag_start: String
194
+
195
+ attr_reader universal_markup_end: bool
196
+
197
+ attr_reader re_tag_name: Regexp
198
+
199
+ attr_reader re_word: Regexp
200
+
201
+ attr_reader re_int: Regexp
202
+
203
+ attr_reader re_float: Regexp
204
+
205
+ attr_reader re_double_quote_string_special: Regexp
206
+
207
+ attr_reader re_single_quote_string_special: Regexp
208
+
209
+ attr_reader re_markup_start: Regexp
210
+
211
+ attr_reader re_markup_end: Regexp
212
+
213
+ attr_reader re_markup_end_chars: Regexp
214
+
215
+ attr_reader re_up_to_markup_start: Regexp
216
+
217
+ attr_reader re_punctuation: Regexp
218
+
219
+ attr_reader re_up_to_inline_comment_end: Regexp
220
+
221
+ attr_reader re_up_to_raw_end: Regexp
222
+
223
+ attr_reader re_block_comment_chunk: Regexp
224
+
225
+ attr_reader re_up_to_doc_end: Regexp
226
+
227
+ attr_reader re_line_statement_comment: Regexp
228
+
229
+ def initialize: (?arithmetic_operators: bool, ?context_depth_limit: ::Integer, ?auto_trim: '-' | '~' | nil, ?falsy_undefined: bool, ?globals: untyped?, ?loader: TemplateLoader?, ?local_namespace_limit: Integer?, ?loop_iteration_limit: Integer?, ?markup_comment_prefix: ::String, ?markup_comment_suffix: ::String, ?markup_out_end: ::String, ?markup_out_start: ::String, ?markup_tag_end: ::String, ?markup_tag_start: ::String, ?output_stream_limit: Integer?, ?parser: singleton(Parser), ?scanner: singleton(Scanner), ?shorthand_indexes: bool, ?suppress_blank_control_flow_blocks: bool, ?undefined: singleton(Undefined)) -> void
110
230
 
111
231
  # @param source [String] template source text.
112
232
  # @return [Template]
@@ -136,6 +256,8 @@ module Liquid2
136
256
  def delete_tag: (String name) -> (_Tag | nil)
137
257
 
138
258
  def setup_tags_and_filters: () -> void
259
+
260
+ def setup_scanner: () -> void
139
261
 
140
262
  def undefined: (String name, ?node: _HasToken?) -> Undefined
141
263
 
@@ -171,37 +293,59 @@ module Liquid2
171
293
  # A pointer to the start of the current token.
172
294
  @start: Integer
173
295
 
174
- # Tokens are arrays of (kind, value, start index)
175
- @tokens: Array[[Symbol, String?, Integer]]
296
+ @s_out_start: String
176
297
 
177
- attr_reader tokens: Array[[Symbol, String?, Integer]]
298
+ @s_out_end: String
299
+
300
+ @s_tag_start: String
301
+
302
+ @s_tag_end: String
303
+
304
+ @s_comment_prefix: String
305
+
306
+ @s_comment_suffix: String
178
307
 
179
- RE_MARKUP_START: ::Regexp
308
+ @re_tag_name: Regexp
180
309
 
181
- RE_WHITESPACE: ::Regexp
310
+ @re_word: Regexp
182
311
 
183
- RE_LINE_SPACE: ::Regexp
312
+ @re_int: Regexp
184
313
 
185
- RE_WORD: ::Regexp
314
+ @re_float: Regexp
186
315
 
187
- RE_INT: ::Regexp
316
+ @re_double_quote_string_special: Regexp
188
317
 
189
- RE_FLOAT: ::Regexp
318
+ @re_single_quote_string_special: Regexp
190
319
 
191
- RE_PUNCTUATION: ::Regexp
320
+ @re_markup_start: Regexp
192
321
 
193
- RE_SINGLE_QUOTE_STRING_SPECIAL: ::Regexp
322
+ @re_markup_end: Regexp
194
323
 
195
- RE_DOUBLE_QUOTE_STRING_SPECIAL: ::Regexp
324
+ @re_markup_end_chars: Regexp
325
+
326
+ @re_up_to_markup_start: Regexp
327
+
328
+ @re_punctuation: Regexp
329
+
330
+ @re_up_to_inline_comment_end: Regexp
331
+ @re_up_to_raw_end: Regexp
332
+ @re_block_comment_chunk: Regexp
333
+ @re_up_to_doc_end: Regexp
334
+ @re_line_statement_comment: Regexp
335
+
336
+ # Tokens are arrays of (kind, value, start index)
337
+ @tokens: Array[[Symbol, String?, Integer]]
338
+
339
+ attr_reader tokens: Array[[Symbol, String?, Integer]]
196
340
 
197
341
  # Keywords and symbols that get their own token kind.
198
342
  TOKEN_MAP: Hash[String, Symbol]
199
343
 
200
- def self.tokenize: (String source, StringScanner scanner) -> Array[[Symbol, String?, Integer]]
344
+ def self.tokenize: (Environment env, String source, StringScanner scanner) -> Array[[Symbol, String?, Integer]]
201
345
 
202
346
  # @param source [String]
203
347
  # @param scanner [StringScanner]
204
- def initialize: (String source, StringScanner scanner) -> void
348
+ def initialize: (Environment env, String source, StringScanner scanner) -> void
205
349
 
206
350
  def run: () -> void
207
351
 
@@ -253,6 +397,8 @@ module Liquid2
253
397
 
254
398
  @whitespace_carry: String?
255
399
 
400
+ @output_end: Symbol
401
+
256
402
  # Parse Liquid template text into a syntax tree.
257
403
  # @param source [String]
258
404
  # @return [Array[Node | String]]
@@ -378,13 +524,13 @@ module Liquid2
378
524
 
379
525
  MEMBERSHIP: 6
380
526
 
381
- PREFIX: 7
382
-
383
527
  ADD_SUB: 8
384
528
 
385
529
  MUL_DIV: 9
386
530
 
387
531
  POW: 10
532
+
533
+ PREFIX: 11
388
534
  end
389
535
 
390
536
  PRECEDENCES: Hash[Symbol, Integer]
@@ -1760,6 +1906,8 @@ module Liquid2
1760
1906
 
1761
1907
  # Return the subsequence of _left_ starting at _start_ up to _length_.
1762
1908
  def self.slice: (untyped left, untyped start, ?untyped length) -> untyped
1909
+
1910
+ def self.better_slice: (untyped left, ?untyped start_, ?untyped stop_, ?untyped step_, ?start: untyped, ?stop: untyped, ?step: untyped) -> untyped
1763
1911
 
1764
1912
  # Return _left_ with all characters converted to uppercase.
1765
1913
  # Coerce _left_ to a string if it is not one already.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Prior
metadata.gz.sig CHANGED
Binary file