From f24ca042a272b01f47fe1f13a5e028bd52f29703 Mon Sep 17 00:00:00 2001 From: Tobi Lutke Date: Wed, 11 Mar 2026 08:33:19 -0400 Subject: [PATCH] avoid array allocation in parse_tag_token: return tag_name, store markup/newlines as class ivars --- lib/liquid/block_body.rb | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb index 54050a01..c7a91a66 100644 --- a/lib/liquid/block_body.rb +++ b/lib/liquid/block_body.rb @@ -16,8 +16,13 @@ module Liquid # Parses "{%[-] tag_name markup [-]%}" and returns [pre_ws, tag_name, post_ws, markup] or nil NEWLINE_BYTE = 10 # "\n".ord + class << self + attr_reader :_last_markup, :_last_newlines + end + # Fast manual tag token parser - avoids regex MatchData allocation - # Parses "{%[-] tag_name markup [-]%}" and returns [tag_name, markup, newline_count] or nil + # Parses "{%[-] tag_name markup [-]%}" directly into parse_context fields + # Returns tag_name string or nil on failure. Sets @_tag_markup and @_tag_newlines. def self.parse_tag_token(token) # token starts with "{%" pos = 2 @@ -47,11 +52,11 @@ module Liquid else while pos < len b = token.getbyte(pos) - break unless (b >= 97 && b <= 122) || (b >= 65 && b <= 90) || (b >= 48 && b <= 57) || b == 95 # a-z, A-Z, 0-9, _ + break unless (b >= 97 && b <= 122) || (b >= 65 && b <= 90) || (b >= 48 && b <= 57) || b == 95 pos += 1 end end - return nil if pos == name_start # no tag name found + return nil if pos == name_start tag_name = token.byteslice(name_start, pos - name_start) # skip post-whitespace, counting newlines @@ -68,12 +73,15 @@ module Liquid end # the rest is markup, up to optional '-' and '%}' - # token ends with '%}' (guaranteed by tokenizer) markup_end = len - 2 - markup_end -= 1 if markup_end > pos && token.getbyte(markup_end - 1) == 45 # trailing '-' + markup_end -= 1 if markup_end > pos && token.getbyte(markup_end - 1) == 45 markup = pos >= markup_end ? "" : token.byteslice(pos, markup_end - pos) - [tag_name, markup, newlines] + # Store extra results to avoid array allocation for the return value + @_last_markup = markup + @_last_newlines = newlines + + tag_name end attr_reader :nodelist @@ -200,14 +208,15 @@ module Liquid second_byte = token.getbyte(1) if second_byte == PERCENT_BYTE whitespace_handler(token, parse_context) - parsed = BlockBody.parse_tag_token(token) - unless parsed + tag_name = BlockBody.parse_tag_token(token) + unless tag_name return handle_invalid_tag_token(token, parse_context, &block) end - tag_name, markup, newlines = parsed + markup = BlockBody._last_markup - if parse_context.line_number && newlines > 0 - parse_context.line_number += newlines + if parse_context.line_number + newlines = BlockBody._last_newlines + parse_context.line_number += newlines if newlines > 0 end if tag_name == 'liquid'