mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-20 11:20:41 -07:00
Introduce the AST Optimizer
... that can only optimize one thing: combine `| append: 'a' | append: 'b'` into a single `| append_all: ['a', 'b']` filter call. This avoids all the intermediate allocations caused by several `append`. Also introduce a new filter `append_all: [*items]`.
This commit is contained in:
@@ -84,6 +84,7 @@ require 'liquid/usage'
|
|||||||
require 'liquid/register'
|
require 'liquid/register'
|
||||||
require 'liquid/static_registers'
|
require 'liquid/static_registers'
|
||||||
require 'liquid/template_factory'
|
require 'liquid/template_factory'
|
||||||
|
require 'liquid/optimizer'
|
||||||
|
|
||||||
# Load all the tags of the standard library
|
# Load all the tags of the standard library
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Liquid
|
||||||
|
class Optimizer
|
||||||
|
def optimize(node)
|
||||||
|
case node
|
||||||
|
when Liquid::Template then optimize(node.root.body)
|
||||||
|
when Liquid::Document then optimize(node.body)
|
||||||
|
when Liquid::BlockBody then optimize_block(node)
|
||||||
|
when Liquid::Variable then optimize_variable(node)
|
||||||
|
when Liquid::Assign then optimize_assign(node)
|
||||||
|
when Liquid::For then optimize_for(node)
|
||||||
|
when Liquid::If then optimize_if(node)
|
||||||
|
end
|
||||||
|
node
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimize_block(block)
|
||||||
|
block.nodelist.each { |node| optimize(node) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimize_variable(node)
|
||||||
|
# Turn chained `| append: "..."| append: "..."`, into a single `append_all: [...]`
|
||||||
|
if node.filters.size > 1 && node.filters.all? { |f, _| f == "append" }
|
||||||
|
node.filters = [["append_all", node.filters.map { |f, (arg)| arg }]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimize_assign(node)
|
||||||
|
optimize(node.from)
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimize_for(node)
|
||||||
|
optimize(node.collection_name)
|
||||||
|
optimize_block(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimize_if(node)
|
||||||
|
node.blocks.each do |block|
|
||||||
|
optimize_condition(block)
|
||||||
|
optimize(block.attachment)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimize_condition(node)
|
||||||
|
case node
|
||||||
|
when Liquid::ElseCondition
|
||||||
|
# noop
|
||||||
|
when Liquid::Condition
|
||||||
|
optimize(node.left)
|
||||||
|
optimize(node.right) if node.right
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -281,6 +281,10 @@ module Liquid
|
|||||||
input.to_s + string.to_s
|
input.to_s + string.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def append_all(input, *items)
|
||||||
|
input.to_s + items.join
|
||||||
|
end
|
||||||
|
|
||||||
def concat(input, array)
|
def concat(input, array)
|
||||||
unless array.respond_to?(:to_ary)
|
unless array.respond_to?(:to_ary)
|
||||||
raise ArgumentError, "concat filter requires an array argument"
|
raise ArgumentError, "concat filter requires an array argument"
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class OptimizerUnitTest < Minitest::Test
|
||||||
|
include Liquid
|
||||||
|
|
||||||
|
def test_combines_append_filters
|
||||||
|
optimizer = Optimizer.new
|
||||||
|
var = Variable.new('hello | append: "a" | append: b', ParseContext.new)
|
||||||
|
var = optimizer.optimize(var)
|
||||||
|
assert_equal([
|
||||||
|
['append_all', ["a", VariableLookup.new("b")]]
|
||||||
|
], var.filters)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user