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