mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-20 03:10:39 -07:00
lazy tokenizer
This commit is contained in:
+88
-13
@@ -1,19 +1,30 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "strscan"
|
||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class Tokenizer
|
class Tokenizer
|
||||||
attr_reader :line_number, :for_liquid_tag
|
attr_reader :line_number, :for_liquid_tag
|
||||||
|
|
||||||
|
TAG_START = /\{%/
|
||||||
|
TAG_END = /%\}/
|
||||||
|
VARIABLE_START = /\{\{/
|
||||||
|
VARIABLE_END = /\}\}/
|
||||||
|
TAG_OR_VARIABLE_START = /\{[\{\%}]/
|
||||||
|
|
||||||
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
|
||||||
@source = source
|
@source = source
|
||||||
@line_number = line_number || (line_numbers ? 1 : nil)
|
@line_number = line_number || (line_numbers ? 1 : nil)
|
||||||
@for_liquid_tag = for_liquid_tag
|
@for_liquid_tag = for_liquid_tag
|
||||||
@offset = 0
|
@offset = 0
|
||||||
@tokens = tokenize
|
@ss = StringScanner.new(source)
|
||||||
end
|
end
|
||||||
|
|
||||||
def shift
|
def shift
|
||||||
token = @tokens[@offset]
|
return nil if @ss.eos?
|
||||||
|
|
||||||
|
token = @for_liquid_tag ? next_liquid_token : next_token
|
||||||
|
|
||||||
return nil unless token
|
return nil unless token
|
||||||
|
|
||||||
@offset += 1
|
@offset += 1
|
||||||
@@ -27,19 +38,83 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def tokenize
|
def next_liquid_token
|
||||||
return [] if @source.empty?
|
# read until we find a \n
|
||||||
|
start = @ss.pos
|
||||||
return @source.split("\n") if @for_liquid_tag
|
if @ss.scan_until(/\n/).nil?
|
||||||
|
token = @ss.rest
|
||||||
tokens = @source.split(TemplateParser)
|
@ss.terminate
|
||||||
|
return token
|
||||||
# removes the rogue empty element at the beginning of the array
|
|
||||||
if tokens[0]&.empty?
|
|
||||||
@offset += 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
tokens
|
@ss.string.byteslice(start, @ss.pos - start - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_token
|
||||||
|
# possible states: :text, :tag, :variable
|
||||||
|
c_list = @ss.peek(2)
|
||||||
|
|
||||||
|
case c_list
|
||||||
|
when "{%"
|
||||||
|
next_tag_token
|
||||||
|
when "{{"
|
||||||
|
next_variable_token
|
||||||
|
else
|
||||||
|
next_text_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_text_token
|
||||||
|
start = @ss.pos
|
||||||
|
|
||||||
|
if @ss.scan_until(TAG_OR_VARIABLE_START).nil?
|
||||||
|
token = @ss.rest
|
||||||
|
@ss.terminate
|
||||||
|
return token
|
||||||
|
end
|
||||||
|
|
||||||
|
@ss.pos -= 2
|
||||||
|
|
||||||
|
@ss.string.byteslice(start, @ss.pos - start)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_variable_token
|
||||||
|
start = @ss.pos
|
||||||
|
@ss.pos += 2
|
||||||
|
found_variable_end = false
|
||||||
|
|
||||||
|
# it is possible to see a {% before a }} so we need to check for that
|
||||||
|
until @ss.eos?
|
||||||
|
case @ss.peek(2)
|
||||||
|
when "}}"
|
||||||
|
@ss.pos += 2
|
||||||
|
found_variable_end = true
|
||||||
|
break
|
||||||
|
when "{%"
|
||||||
|
return next_tag_token(start)
|
||||||
|
end
|
||||||
|
|
||||||
|
@ss.pos += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return "{{" unless found_variable_end
|
||||||
|
|
||||||
|
@ss.string.byteslice(start, @ss.pos - start)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_tag_token(start = nil)
|
||||||
|
start = @ss.pos if start.nil?
|
||||||
|
@ss.pos += 2
|
||||||
|
|
||||||
|
if @ss.scan_until(TAG_END).nil?
|
||||||
|
raise_syntax_error("tag was not properly terminated")
|
||||||
|
end
|
||||||
|
|
||||||
|
@ss.string.byteslice(start, @ss.pos - start)
|
||||||
|
end
|
||||||
|
|
||||||
|
def raise_syntax_error(message)
|
||||||
|
raise SyntaxError, message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user