mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-20 11:20:41 -07:00
various speedups
This commit is contained in:
@@ -80,7 +80,7 @@ namespace :profile do
|
|||||||
|
|
||||||
desc "Run KCacheGrind"
|
desc "Run KCacheGrind"
|
||||||
task :grind => :run do
|
task :grind => :run do
|
||||||
system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt"
|
system "qcachegrind /tmp//callgrind.liquid.txt"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+1
-1
@@ -109,7 +109,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
output.join
|
output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+60
-61
@@ -112,76 +112,76 @@ module Liquid
|
|||||||
@scopes[0] = {}
|
@scopes[0] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Look up variable, either resolve directly after considering the name. We can directly handle
|
||||||
|
# Strings, digits, floats and booleans (true,false).
|
||||||
|
# If no match is made we lookup the variable in the current scope and
|
||||||
|
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
||||||
|
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# products == empty #=> products.empty?
|
||||||
|
def resolve(key)
|
||||||
|
case key
|
||||||
|
when nil, ""
|
||||||
|
return nil
|
||||||
|
when "blank"
|
||||||
|
return :blank?
|
||||||
|
when "empty"
|
||||||
|
return :empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
result = Parser.parse(key)
|
||||||
|
stack = []
|
||||||
|
|
||||||
|
result.each do |(sym, value)|
|
||||||
|
|
||||||
|
case sym
|
||||||
|
when :id
|
||||||
|
stack.push value
|
||||||
|
when :lookup
|
||||||
|
left = stack.pop
|
||||||
|
value = find_variable(left)
|
||||||
|
|
||||||
|
stack.push(harden(value))
|
||||||
|
when :range
|
||||||
|
right = stack.pop.to_i
|
||||||
|
left = stack.pop.to_i
|
||||||
|
|
||||||
|
stack.push (left..right)
|
||||||
|
when :buildin
|
||||||
|
left = stack.pop
|
||||||
|
value = invoke_buildin(left, value)
|
||||||
|
|
||||||
|
stack.push(harden(value))
|
||||||
|
when :call
|
||||||
|
left = stack.pop
|
||||||
|
right = stack.pop
|
||||||
|
value = lookup_and_evaluate(right, left)
|
||||||
|
|
||||||
|
stack.push(harden(value))
|
||||||
|
else
|
||||||
|
raise "unknown #{sym}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return stack.first
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
||||||
def []=(key, value)
|
def []=(key, value)
|
||||||
@scopes[0][key] = value
|
@scopes[0][key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](key)
|
|
||||||
resolve(key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_key?(key)
|
def has_key?(key)
|
||||||
resolve(key) != nil
|
resolve(key) != nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias_method :[], :resolve
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Look up variable, either resolve directly after considering the name. We can directly handle
|
|
||||||
# Strings, digits, floats and booleans (true,false).
|
|
||||||
# If no match is made we lookup the variable in the current scope and
|
|
||||||
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
|
||||||
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# products == empty #=> products.empty?
|
|
||||||
def resolve(key)
|
|
||||||
case key
|
|
||||||
when nil, ""
|
|
||||||
return nil
|
|
||||||
when "blank"
|
|
||||||
return :blank?
|
|
||||||
when "empty"
|
|
||||||
return :empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
result = Parser.parse(key)
|
|
||||||
stack = []
|
|
||||||
|
|
||||||
result.each do |(sym, value)|
|
|
||||||
|
|
||||||
case sym
|
|
||||||
when :id
|
|
||||||
stack.push value
|
|
||||||
when :lookup
|
|
||||||
left = stack.pop
|
|
||||||
value = find_variable(left)
|
|
||||||
|
|
||||||
stack.push(harden(value))
|
|
||||||
when :range
|
|
||||||
right = stack.pop.to_i
|
|
||||||
left = stack.pop.to_i
|
|
||||||
|
|
||||||
stack.push (left..right)
|
|
||||||
when :buildin
|
|
||||||
left = stack.pop
|
|
||||||
value = invoke_buildin(left, value)
|
|
||||||
|
|
||||||
stack.push(harden(value))
|
|
||||||
when :call
|
|
||||||
left = stack.pop
|
|
||||||
right = stack.pop
|
|
||||||
value = lookup_and_evaluate(right, left)
|
|
||||||
|
|
||||||
stack.push(harden(value))
|
|
||||||
else
|
|
||||||
raise "unknown #{sym}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return stack.first
|
|
||||||
end
|
|
||||||
|
|
||||||
def invoke_buildin(obj, key)
|
def invoke_buildin(obj, key)
|
||||||
# as weird as this is, liquid unit tests demand that we prioritize hash lookups
|
# as weird as this is, liquid unit tests demand that we prioritize hash lookups
|
||||||
# to buildins. So if we got a hash and it has a :first element we need to call that
|
# to buildins. So if we got a hash and it has a :first element we need to call that
|
||||||
@@ -226,8 +226,7 @@ module Liquid
|
|||||||
|
|
||||||
value = obj[key]
|
value = obj[key]
|
||||||
|
|
||||||
case value
|
if value.is_a?(Proc)
|
||||||
when Proc
|
|
||||||
# call the proc
|
# call the proc
|
||||||
value = (value.arity == 0) ? value.call : value.call(self)
|
value = (value.arity == 0) ? value.call : value.call(self)
|
||||||
|
|
||||||
|
|||||||
+61
-16
@@ -6,6 +6,8 @@ module Liquid
|
|||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = $1
|
@variable_name = $1
|
||||||
@collection_name = $2
|
@collection_name = $2
|
||||||
|
@idx_i = "#{$1}-#{$2}-i"
|
||||||
|
@idx_col = "#{$1}-#{$2}-c"
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = value
|
@attributes[key] = value
|
||||||
@@ -18,6 +20,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
|
context.registers[:tablerowloop] ||= Hash.new(0)
|
||||||
|
|
||||||
collection = context[@collection_name] or return ''
|
collection = context[@collection_name] or return ''
|
||||||
|
|
||||||
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
|
from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0
|
||||||
@@ -32,26 +36,15 @@ module Liquid
|
|||||||
row = 1
|
row = 1
|
||||||
col = 0
|
col = 0
|
||||||
|
|
||||||
result = "<tr class=\"row1\">\n"
|
result = ["<tr class=\"row1\">\n"]
|
||||||
context.stack do
|
context.stack do
|
||||||
|
|
||||||
|
context.registers[:tablerowloop][@idx]
|
||||||
|
context['tablerowloop'] = lambda { Tablerowloop.new(@idx_i, @idx_col, length) }
|
||||||
collection.each_with_index do |item, index|
|
collection.each_with_index do |item, index|
|
||||||
|
context.registers[:tablerowloop][@idx_i] = index
|
||||||
|
context.registers[:tablerowloop][@idx_col] = col
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
context['tablerowloop'] = {
|
|
||||||
'length' => length,
|
|
||||||
'index' => index + 1,
|
|
||||||
'index0' => index,
|
|
||||||
'col' => col + 1,
|
|
||||||
'col0' => col,
|
|
||||||
'index0' => index,
|
|
||||||
'rindex' => length - index,
|
|
||||||
'rindex0' => length - index - 1,
|
|
||||||
'first' => (index == 0),
|
|
||||||
'last' => (index == length - 1),
|
|
||||||
'col_first' => (col == 0),
|
|
||||||
'col_last' => (col == cols - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
col += 1
|
col += 1
|
||||||
|
|
||||||
@@ -68,6 +61,58 @@ module Liquid
|
|||||||
result << "</tr>\n"
|
result << "</tr>\n"
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
class Tablerowloop < Liquid::Drop
|
||||||
|
attr_accessor :length
|
||||||
|
|
||||||
|
def initialize(idx_i, idx_col, length)
|
||||||
|
@idx_i, @idx_col, @length = idx_i, idx_col, length
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@context.registers[:tablerowloop][@idx_i] + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def index0
|
||||||
|
@context.registers[:tablerowloop][@idx_i]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex
|
||||||
|
length - @context.registers[:tablerowloop][@idx_i]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex0
|
||||||
|
length - @context.registers[:tablerowloop][@idx_i] - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def first
|
||||||
|
(@context.registers[:tablerowloop][@idx_i] == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def last
|
||||||
|
(@context.registers[:tablerowloop][@idx_i] == length - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def col
|
||||||
|
@context.registers[:tablerowloop][@idx_col] + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def col0
|
||||||
|
@context.registers[:tablerowloop][@idx_col]
|
||||||
|
end
|
||||||
|
|
||||||
|
def col_first
|
||||||
|
(@context.registers[:tablerowloop][@idx_col] == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def col_last
|
||||||
|
(@context.registers[:tablerowloop][@idx_col] == cols - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Template.register_tag('tablerow', TableRow)
|
Template.register_tag('tablerow', TableRow)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ module Liquid
|
|||||||
context.stack do
|
context.stack do
|
||||||
execute_else_block = true
|
execute_else_block = true
|
||||||
|
|
||||||
output = ''
|
output = []
|
||||||
@blocks.each do |block|
|
@blocks.each do |block|
|
||||||
if block.else?
|
if block.else?
|
||||||
return render_all(block.attachment, context) if execute_else_block
|
return render_all(block.attachment, context) if execute_else_block
|
||||||
|
|||||||
+37
-11
@@ -51,6 +51,7 @@ module Liquid
|
|||||||
@variable_name = $1
|
@variable_name = $1
|
||||||
@collection_name = $2
|
@collection_name = $2
|
||||||
@name = "#{$1}-#{$2}"
|
@name = "#{$1}-#{$2}"
|
||||||
|
@idx = "#{@name}-i"
|
||||||
@reversed = $3
|
@reversed = $3
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@@ -87,14 +88,13 @@ module Liquid
|
|||||||
limit = context[@attributes['limit']]
|
limit = context[@attributes['limit']]
|
||||||
to = limit ? limit.to_i + from : nil
|
to = limit ? limit.to_i + from : nil
|
||||||
|
|
||||||
|
|
||||||
segment = Utils.slice_collection_using_each(collection, from, to)
|
segment = Utils.slice_collection_using_each(collection, from, to)
|
||||||
|
|
||||||
return render_else(context) if segment.empty?
|
return render_else(context) if segment.empty?
|
||||||
|
|
||||||
segment.reverse! if @reversed
|
segment.reverse! if @reversed
|
||||||
|
|
||||||
result = ''
|
result = []
|
||||||
|
|
||||||
length = segment.length
|
length = segment.length
|
||||||
|
|
||||||
@@ -102,17 +102,10 @@ module Liquid
|
|||||||
context.registers[:for][@name] = from + segment.length
|
context.registers[:for][@name] = from + segment.length
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
|
context['forloop'] = lambda { Forloop.new(@name, @idx, length) }
|
||||||
segment.each_with_index do |item, index|
|
segment.each_with_index do |item, index|
|
||||||
|
context.registers[:for][@idx] = index
|
||||||
context[@variable_name] = item
|
context[@variable_name] = item
|
||||||
context['forloop'] = {
|
|
||||||
'name' => @name,
|
|
||||||
'length' => length,
|
|
||||||
'index' => index + 1,
|
|
||||||
'index0' => index,
|
|
||||||
'rindex' => length - index,
|
|
||||||
'rindex0' => length - index - 1,
|
|
||||||
'first' => (index == 0),
|
|
||||||
'last' => (index == length - 1) }
|
|
||||||
|
|
||||||
result << render_all(@for_block, context)
|
result << render_all(@for_block, context)
|
||||||
|
|
||||||
@@ -129,6 +122,39 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
class Forloop < Liquid::Drop
|
||||||
|
attr_accessor :name, :length
|
||||||
|
|
||||||
|
def initialize(name, idx, length)
|
||||||
|
@name, @idx, @length = name, idx, length
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@context.registers[:for][@idx] + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def index0
|
||||||
|
@context.registers[:for][@idx]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex
|
||||||
|
length - @context.registers[:for][@idx]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rindex0
|
||||||
|
length - @context.registers[:for][@idx] - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def first
|
||||||
|
(@context.registers[:for][@idx] == 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def last
|
||||||
|
(@context.registers[:for][@idx] == length - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def render_else(context)
|
def render_else(context)
|
||||||
return @else_block ? [render_all(@else_block, context)] : ''
|
return @else_block ? [render_all(@else_block, context)] : ''
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -121,8 +121,7 @@ module Liquid
|
|||||||
begin
|
begin
|
||||||
# render the nodelist.
|
# render the nodelist.
|
||||||
# for performance reasons we get a array back here. join will make a string out of it
|
# for performance reasons we get a array back here. join will make a string out of it
|
||||||
result = @root.render(context)
|
@root.render(context).join
|
||||||
result.respond_to?(:join) ? result.join : result
|
|
||||||
ensure
|
ensure
|
||||||
@errors = context.errors
|
@errors = context.errors
|
||||||
end
|
end
|
||||||
|
|||||||
Binary file not shown.
@@ -4,8 +4,8 @@ require File.dirname(__FILE__) + '/theme_runner'
|
|||||||
|
|
||||||
profiler = ThemeRunner.new
|
profiler = ThemeRunner.new
|
||||||
|
|
||||||
Benchmark.bmbm do |x|
|
Benchmark.bm do |x|
|
||||||
x.report("parse:") { 100.times { profiler.compile } }
|
# x.report("parse:") { 100.times { profiler.compile } }
|
||||||
x.report("parse & run:") { 100.times { profiler.run } }
|
x.report("parse & run:") { 100.times { profiler.run } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class ThemeRunner
|
|||||||
# Dup assigns because will make some changes to them
|
# Dup assigns because will make some changes to them
|
||||||
|
|
||||||
@tests.each do |liquid, layout, template_name|
|
@tests.each do |liquid, layout, template_name|
|
||||||
|
|
||||||
tmpl = Liquid::Template.new
|
tmpl = Liquid::Template.new
|
||||||
tmpl.parse(liquid)
|
tmpl.parse(liquid)
|
||||||
tmpl = Liquid::Template.new
|
tmpl = Liquid::Template.new
|
||||||
@@ -54,7 +53,7 @@ class ThemeRunner
|
|||||||
|
|
||||||
|
|
||||||
def run_profile
|
def run_profile
|
||||||
RubyProf.measure_mode = RubyProf::WALL_TIME
|
RubyProf.measure_mode = RubyProf::PROCESS_TIME
|
||||||
|
|
||||||
# Dup assigns because will make some changes to them
|
# Dup assigns because will make some changes to them
|
||||||
assigns = Database.tables.dup
|
assigns = Database.tables.dup
|
||||||
|
|||||||
Reference in New Issue
Block a user