diff --git a/Rakefile b/Rakefile index ecd44b6..5fdcef8 100755 --- a/Rakefile +++ b/Rakefile @@ -80,7 +80,7 @@ namespace :profile do desc "Run KCacheGrind" task :grind => :run do - system "qcachegrind /tmp/liquid.rubyprof_calltreeprinter.txt" + system "qcachegrind /tmp//callgrind.liquid.txt" end end diff --git a/ext/liquid/liquid_ext.bundle b/ext/liquid/liquid_ext.bundle index e35a56b..68dd5b6 100755 Binary files a/ext/liquid/liquid_ext.bundle and b/ext/liquid/liquid_ext.bundle differ diff --git a/ext/liquid/liquid_ext.o b/ext/liquid/liquid_ext.o index 4e97bfc..6591f1e 100644 Binary files a/ext/liquid/liquid_ext.o and b/ext/liquid/liquid_ext.o differ diff --git a/lib/liquid/block.rb b/lib/liquid/block.rb index 8ea87b0..0b49a81 100644 --- a/lib/liquid/block.rb +++ b/lib/liquid/block.rb @@ -109,7 +109,7 @@ module Liquid end end - output.join + output end end end diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index b9d9136..0f08ec6 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -115,76 +115,76 @@ module Liquid @scopes[0] = {} 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 Liquid::Drop def []=(key, value) @scopes[0][key] = value end - def [](key) - resolve(key) - end - def has_key?(key) resolve(key) != nil end + alias_method :[], :resolve + 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) # 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 @@ -229,8 +229,7 @@ module Liquid value = obj[key] - case value - when Proc + if value.is_a?(Proc) # call the proc value = (value.arity == 0) ? value.call : value.call(self) diff --git a/lib/liquid/htmltags.rb b/lib/liquid/htmltags.rb index 78424e6..1b40042 100644 --- a/lib/liquid/htmltags.rb +++ b/lib/liquid/htmltags.rb @@ -6,6 +6,8 @@ module Liquid if markup =~ Syntax @variable_name = $1 @collection_name = $2 + @idx_i = "#{$1}-#{$2}-i" + @idx_col = "#{$1}-#{$2}-c" @attributes = {} markup.scan(TagAttributes) do |key, value| @attributes[key] = value @@ -18,6 +20,8 @@ module Liquid end def render(context) + context.registers[:tablerowloop] ||= Hash.new(0) + collection = context[@collection_name] or return '' from = @attributes['offset'] ? context[@attributes['offset']].to_i : 0 @@ -32,26 +36,15 @@ module Liquid row = 1 col = 0 - result = "\n" + result = ["\n"] context.stack do + context.registers[:tablerowloop][@idx] + context['tablerowloop'] = lambda { Tablerowloop.new(@idx_i, @idx_col, length) } collection.each_with_index do |item, index| - 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) - } - + context.registers[:tablerowloop][@idx_i] = index + context.registers[:tablerowloop][@idx_col] = col + context[@variable_name] = item col += 1 @@ -68,6 +61,58 @@ module Liquid result << "\n" result 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 Template.register_tag('tablerow', TableRow) diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 4e2fb2c..28957af 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -31,7 +31,7 @@ module Liquid context.stack do execute_else_block = true - output = '' + output = [] @blocks.each do |block| if block.else? return render_all(block.attachment, context) if execute_else_block diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index 8d2b27b..dddc373 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -50,7 +50,8 @@ module Liquid if markup =~ Syntax @variable_name = $1 @collection_name = $2 - @name = "#{$1}-#{$2}" + @name = "#{$1}-#{$2}" + @idx = "#{@name}-i" @reversed = $3 @attributes = {} markup.scan(TagAttributes) do |key, value| @@ -87,14 +88,13 @@ module Liquid limit = context[@attributes['limit']] to = limit ? limit.to_i + from : nil - segment = Utils.slice_collection_using_each(collection, from, to) return render_else(context) if segment.empty? segment.reverse! if @reversed - result = '' + result = [] length = segment.length @@ -102,17 +102,10 @@ module Liquid context.registers[:for][@name] = from + segment.length context.stack do + context['forloop'] = lambda { Forloop.new(@name, @idx, length) } segment.each_with_index do |item, index| + context.registers[:for][@idx] = index 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) @@ -129,6 +122,39 @@ module Liquid 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) return @else_block ? [render_all(@else_block, context)] : '' end diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 1d01982..b26825b 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -121,8 +121,7 @@ module Liquid begin # render the nodelist. # for performance reasons we get a array back here. join will make a string out of it - result = @root.render(context) - result.respond_to?(:join) ? result.join : result + @root.render(context).join ensure @errors = context.errors end diff --git a/lib/liquid_ext.bundle b/lib/liquid_ext.bundle index e35a56b..68dd5b6 100755 Binary files a/lib/liquid_ext.bundle and b/lib/liquid_ext.bundle differ diff --git a/performance/benchmark.rb b/performance/benchmark.rb index afb6ffa..b9fd354 100644 --- a/performance/benchmark.rb +++ b/performance/benchmark.rb @@ -4,8 +4,8 @@ require File.dirname(__FILE__) + '/theme_runner' profiler = ThemeRunner.new -Benchmark.bmbm do |x| - x.report("parse:") { 100.times { profiler.compile } } +Benchmark.bm do |x| +# x.report("parse:") { 100.times { profiler.compile } } x.report("parse & run:") { 100.times { profiler.run } } end diff --git a/performance/theme_runner.rb b/performance/theme_runner.rb index 98406b3..08defee 100644 --- a/performance/theme_runner.rb +++ b/performance/theme_runner.rb @@ -31,7 +31,6 @@ class ThemeRunner # Dup assigns because will make some changes to them @tests.each do |liquid, layout, template_name| - tmpl = Liquid::Template.new tmpl.parse(liquid) tmpl = Liquid::Template.new @@ -54,7 +53,7 @@ class ThemeRunner def run_profile - RubyProf.measure_mode = RubyProf::WALL_TIME + RubyProf.measure_mode = RubyProf::PROCESS_TIME # Dup assigns because will make some changes to them assigns = Database.tables.dup