<commit_before>require 'cgi'
require 'tempo/partial_context'
require 'tempo/standard_helper_context'

module Tempo
  class Runtime

    def initialize
      yield self if block_given?
    end

    attr_writer :partials, :helpers

    def partials
      @partials ||= Tempo::PartialContext.new
    end

    def helpers
      @helpers ||= Tempo::StandardHelperContext.new
    end

    def render(template, context)
      visit(template, context)
    end

  private

    def visit(node, context)
      send("visit_#{node.class.name.split('::').last}", node, context)
    end

    def visit_String(node, context)
      template = Parser.parse(Lexer.lex(node))
      visit(template, context)
    end

    def visit_TemplateNode(node, context)
      node.statements.each_with_object('') do |statement, output|
        output << visit(statement, context)
      end
    end

    def visit_ContentNode(node, context)
      node.value
    end

    def visit_UnescapedExpressionNode(node, context)
      arguments = node.params.map { |p| visit(p, context) }
      options = node.hash && visit(node.hash, context) || {}

      if node.path.kind_of?(Nodes::CallNode) && helper = helpers.lookup(node.path.id)
        helper.call(*arguments, options)
      else
        visit(node.path, context)
      end.to_s
    end

    def visit_ExpressionNode(node, context)
      escape(visit_UnescapedExpressionNode(node, context))
    end

    def visit_CallNode(node, context)
      context = context.to_tempo_context
      context && context.invoke(node.id)
    end

    def visit_PathNode(node, context)
      node.ids.each_with_index.inject(context) do |ctx, (segment, index)|
        if index == 0 && helper = helpers.lookup(segment.id)
          helper.call
        else
          visit(segment, ctx)
        end
      end
    end

    def visit_CommentNode(node, context)
      ''
    end

    def visit_BlockExpressionNode(node, context)
      arguments = node.params.map { |p| visit(p, context) }
      options = node.hash && visit(node.hash, context) || {}

      conditional = if node.path.kind_of?(Nodes::CallNode) && helper = helpers.lookup(node.path.id)
        helper
      else
        visit(node.path, context)
      end

      if conditional.respond_to?(:call)
        conditional.call(*arguments, options) do |variant, local_context, local_variables|
          variant, local_context, local_variables, = :template, variant, local_context unless variant.kind_of?(Symbol)

          local_variables_stack.push(local_variables)

          result = visit(node.send(variant), local_context || context)

          local_variables_stack.pop

          result
        end.to_s
      elsif conditional.respond_to?(:each)
        conditional.enum_for(:each).each_with_index.inject('') do |output, (child, index)|
          local_variables_stack.push({ 'index' => index })
          output << visit(node.template, child)
          local_variables_stack.pop
          output
        end
      elsif conditional
        visit(node.template, context)
      else
        visit(node.inverse, context)
      end
    end<commit_msg>Fixes support for inverse block sections<commit_after>require 'cgi'
require 'tempo/partial_context'
require 'tempo/standard_helper_context'

module Tempo
  class Runtime

    def initialize
      yield self if block_given?
    end

    attr_writer :partials, :helpers

    def partials
      @partials ||= Tempo::PartialContext.new
    end

    def helpers
      @helpers ||= Tempo::StandardHelperContext.new
    end

    def render(template, context)
      visit(template, context)
    end

  private

    def visit(node, context)
      send("visit_#{node.class.name.split('::').last}", node, context)
    end

    def visit_String(node, context)
      template = Parser.parse(Lexer.lex(node))
      visit(template, context)
    end

    def visit_TemplateNode(node, context)
      node.statements.each_with_object('') do |statement, output|
        output << visit(statement, context)
      end
    end

    def visit_ContentNode(node, context)
      node.value
    end

    def visit_UnescapedExpressionNode(node, context)
      arguments = node.params.map { |p| visit(p, context) }
      options = node.hash && visit(node.hash, context) || {}

      if node.path.kind_of?(Nodes::CallNode) && helper = helpers.lookup(node.path.id)
        helper.call(*arguments, options)
      else
        visit(node.path, context)
      end.to_s
    end

    def visit_ExpressionNode(node, context)
      escape(visit_UnescapedExpressionNode(node, context))
    end

    def visit_CallNode(node, context)
      context = context.to_tempo_context
      context && context.invoke(node.id)
    end

    def visit_PathNode(node, context)
      node.ids.each_with_index.inject(context) do |ctx, (segment, index)|
        if index == 0 && helper = helpers.lookup(segment.id)
          helper.call
        else
          visit(segment, ctx)
        end
      end
    end

    def visit_CommentNode(node, context)
      ''
    end

    def visit_BlockExpressionNode(node, context)
      arguments = node.params.map { |p| visit(p, context) }
      options = node.hash && visit(node.hash, context) || {}

      conditional = if node.path.kind_of?(Nodes::CallNode) && helper = helpers.lookup(node.path.id)
        helper
      else
        visit(node.path, context)
      end

      if conditional.respond_to?(:call)
        conditional.call(*arguments, options) do |variant, local_context, local_variables|
          variant, local_context, local_variables, = :template, variant, local_context unless variant.kind_of?(Symbol)

          local_variables_stack.push(local_variables)
          result = visit(node.send(variant), local_context || context)
          local_variables_stack.pop
          result
        end.to_s
      elsif conditional.respond_to?(:each) && conditional.enum_for(:each).count > 0
        conditional.enum_for(:each).each_with_index.inject('') do |output, (child, index)|
          local_variables_stack.push({ 'index' => index })
          output << visit(node.template, child)
          local_variables_stack.pop
          output
        end
      elsif conditional && !(conditional.respond_to?(:empty?) && conditional.empty?)
        visit(node.template, context)
      else
        visit(node.inverse, context)
      end
    end
{'fraction_non_alphanumeric': 0.10479041916167664, 'fraction_numerical': 0.0004727387330601954, 'mean_word_length': 2.713867758923347, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1029894', 'n_tokens_mistral': 1902, 'n_tokens_neox': 1839, 'n_words': 410}