diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb index 2dcc6af..1cf1885 100644 --- a/lib/liquid/context.rb +++ b/lib/liquid/context.rb @@ -12,12 +12,12 @@ module Liquid # # context['bob'] #=> nil class Context class Context - attr_reader :scopes, :errors, :registers, :environments, :resource_limits + attr_reader :scope, :errors, :registers, :environments, :resource_limits attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil) @environments = [environments].flatten - @scopes = [(outer_scope || {})] + @scope = outer_scope || {} @registers = registers @errors = [] @partial = false @@ -25,8 +25,6 @@ module Liquid @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits) squash_instance_assigns_with_environments - @this_stack_used = false - self.exception_renderer = Template.default_exception_renderer if rethrow_errors self.exception_renderer = ->(e) { raise } @@ -35,6 +33,8 @@ module Liquid @interrupts = [] @filters = [] @global_filter = nil + + @stack_level = 0 end def warnings @@ -86,21 +86,9 @@ module Liquid strainer.invoke(method, *args).to_liquid end - # Push new local scope on the stack. use Context#stack instead - def push(new_scope = {}) - @scopes.unshift(new_scope) - raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH - end - # Merge a hash of variables in the current local scope def merge(new_scopes) - @scopes[0].merge!(new_scopes) - end - - # Pop from the stack. use Context#stack instead - def pop - raise ContextError if @scopes.size == 1 - @scopes.shift + @scope.merge!(new_scopes) end # Pushes a new local scope on the stack, pops it at the end of the block @@ -111,32 +99,26 @@ module Liquid # end # # context['var] #=> nil - def stack(new_scope = nil) - old_stack_used = @this_stack_used - if new_scope - push(new_scope) - @this_stack_used = true - else - @this_stack_used = false + def stack(*variable_names) + previous_values = {} + variable_names.each do |variable_name| + previous_values[variable_name] = @scope[variable_name] end - yield - ensure - pop if @this_stack_used - @this_stack_used = old_stack_used - end + @stack_level += 1 + raise StackLevelError, "Nesting too deep".freeze if @stack_level > Block::MAX_DEPTH - def clear_instance_assigns - @scopes[0] = {} + begin + yield + ensure + @scope.merge!(previous_values) + @stack_level -= 1 + end end # Only allow String, Numeric, Hash, Array, Proc, Boolean or Liquid::Drop def []=(key, value) - unless @this_stack_used - @this_stack_used = true - push({}) - end - @scopes[0][key] = value + @scope[key] = value end # Look up variable, either resolve directly after considering the name. We can directly handle @@ -161,26 +143,19 @@ module Liquid # Fetches an object starting at the local scope and then moving up the hierachy def find_variable(key, raise_on_not_found: true) - # This was changed from find() to find_index() because this is a very hot - # path and find_index() is optimized in MRI to reduce object allocation - index = @scopes.find_index { |s| s.key?(key) } - scope = @scopes[index] if index - - variable = nil + scope = @scope if @scope.key?(key) if scope.nil? - @environments.each do |e| + index = @environments.find_index do |e| variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found) # When lookup returned a value OR there is no value but the lookup also did not raise # then it is the value we are looking for. - if !variable.nil? || @strict_variables && raise_on_not_found - scope = e - break - end + !variable.nil? || @strict_variables && raise_on_not_found end + + scope = @environments[index || -1] end - scope ||= @environments.last || @scopes.last variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found) variable = variable.to_liquid @@ -213,10 +188,10 @@ module Liquid end def squash_instance_assigns_with_environments - @scopes.last.each_key do |k| + @scope.each_key do |k| @environments.each do |env| if env.key?(k) - scopes.last[k] = lookup_and_evaluate(env, k) + @scope[k] = lookup_and_evaluate(env, k) break end end diff --git a/lib/liquid/tags/assign.rb b/lib/liquid/tags/assign.rb index 3b696dd..ca630f6 100644 --- a/lib/liquid/tags/assign.rb +++ b/lib/liquid/tags/assign.rb @@ -24,7 +24,7 @@ module Liquid def render(context) val = @from.render(context) - context.scopes.last[@to] = val + context[@to] = val context.resource_limits.assign_score += assign_score_of(val) ''.freeze end diff --git a/lib/liquid/tags/capture.rb b/lib/liquid/tags/capture.rb index d5b8e29..92f25d8 100644 --- a/lib/liquid/tags/capture.rb +++ b/lib/liquid/tags/capture.rb @@ -24,7 +24,7 @@ module Liquid def render(context) output = super - context.scopes.last[@to] = output + context[@to] = output context.resource_limits.assign_score += output.bytesize ''.freeze end diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 5036b27..ed6ab09 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -39,20 +39,19 @@ module Liquid end def render(context) - context.stack do - execute_else_block = true + execute_else_block = true - output = '' - @blocks.each do |block| - if block.else? - return block.attachment.render(context) if execute_else_block - elsif block.evaluate(context) - execute_else_block = false - output << block.attachment.render(context) - end + output = '' + @blocks.each do |block| + if block.else? + return block.attachment.render(context) if execute_else_block + elsif block.evaluate(context) + execute_else_block = false + output << block.attachment.render(context) end - output end + + output end private diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index 17aa860..5dcd058 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -34,15 +34,13 @@ module Liquid def render(context) context.registers[:cycle] ||= {} - context.stack do - key = context.evaluate(@name) - iteration = context.registers[:cycle][key].to_i - result = context.evaluate(@variables[iteration]) - iteration += 1 - iteration = 0 if iteration >= @variables.size - context.registers[:cycle][key] = iteration - result - end + key = context.evaluate(@name) + iteration = context.registers[:cycle][key].to_i + result = context.evaluate(@variables[iteration]) + iteration += 1 + iteration = 0 if iteration >= @variables.size + context.registers[:cycle][key] = iteration + result end private diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index f18fb71..ef68239 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -156,7 +156,7 @@ module Liquid result = '' - context.stack do + context.stack('forloop', @variable_name) do loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1]) for_stack.push(loop_vars) diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index 02da42b..419d2bb 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -40,14 +40,12 @@ module Liquid end def render(context) - context.stack do - @blocks.each do |block| - if block.evaluate(context) - return block.attachment.render(context) - end + @blocks.each do |block| + if block.evaluate(context) + return block.attachment.render(context) end - ''.freeze end + ''.freeze end private diff --git a/lib/liquid/tags/ifchanged.rb b/lib/liquid/tags/ifchanged.rb index d70cbe1..9e9a70b 100644 --- a/lib/liquid/tags/ifchanged.rb +++ b/lib/liquid/tags/ifchanged.rb @@ -1,15 +1,13 @@ module Liquid class Ifchanged < Block def render(context) - context.stack do - output = super + output = super - if output != context.registers[:ifchanged] - context.registers[:ifchanged] = output - output - else - ''.freeze - end + if output != context.registers[:ifchanged] + context.registers[:ifchanged] = output + output + else + ''.freeze end end end diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index c9f2a28..c202144 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -60,7 +60,7 @@ module Liquid begin context.template_name = template_name context.partial = true - context.stack do + context.stack(context_variable_name, *@attributes.keys) do @attributes.each do |key, value| context[key] = context.evaluate(value) end diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 7f391cf..5057a7d 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -31,7 +31,7 @@ module Liquid cols = context.evaluate(@attributes['cols'.freeze]).to_i result = "