whoward/cadenza

View on GitHub
lib/cadenza/source_tree_renderer.rb

Summary

Maintainability
A
25 mins
Test Coverage
# frozen_string_literal: true

require 'stringio'

module Cadenza
  # This renderer will output a string representing the AST in a tree format.  It is intended to be used
  # for debugging purposes.
  class SourceTreeRenderer < BaseRenderer
    # Renders the given AST root node as a source tree (helpful for debugging)
    # @todo move this to a module
    # @see Cadenza::TreeNode#to_tree
    # @param [DocumentNode] document_node the root of the AST you want to render.
    # @param [Context] context the context object to render the document with
    # @return [String] the rendered source tree
    def self.render(document_node, context = {})
      io = StringIO.new
      new(io).render(document_node, context)
      io.string
    end

    def initialize(*args)
      @level = 0
      super
    end

    private

    def render_document(node, context, blocks)
      output << 'Document'
      output << "(extends: #{node.extends})" if node.extends
      output << "\n"
      render_children(node.children, context, blocks)
    end

    def render_constant(node, _context, _blocks)
      output << "Constant(#{node.value.inspect})"
    end

    def render_text(node, _context, _blocks)
      output << "Text(#{node.text.inspect})"
    end

    def render_filter(node, context, blocks)
      output << 'Filter('
      output << node.identifier
      render_parameters(node.parameters, context, blocks)
      output << ')'
    end

    def render_variable(node, context, blocks)
      output << 'Variable('
      output << node.identifier
      render_parameters(node.parameters, context, blocks)
      output << ')'
    end

    def render_operation(node, context, blocks)
      output << "Operation(#{node.operator})\n"
      render_children([node.left, node.right], context, blocks)
    end

    def render_if(node, context, blocks)
      output << "If\n"

      output << "\u2503 Expression:\n"
      render_children([node.expression], context, blocks)
      output << "\n"

      if node.true_children.any?
        output << "\u2503 True Block:\n"
        render_children(node.true_children, context, blocks)
        output << "\n" if node.false_children.any?
      end

      return unless node.false_children.any?

      output << "\u2503 False Block:\n"

      render_children(node.false_children, context, blocks)
    end

    def render_for(node, context, blocks)
      output << "For(iterator: #{node.iterator.identifier}, iterable: #{node.iterable.identifier})\n"
      render_children(node.children, context, blocks)
    end

    def render_generic_block(node, context, blocks)
      output << 'GenericBlock('
      output << node.identifier
      render_parameters(node.parameters, context, blocks)
      output << ")\n"
      render_children(node.children, context, blocks)
    end

    def render_filtered_value(node, context, blocks)
      output << 'FilteredValue'
      render_parameters(node.filters, context, blocks)
      output << "\n"
      render_children([node.value], context, blocks)
    end

    def render_boolean_inverse(node, context, blocks)
      output << "BooleanInverse\n"
      render_children([node.expression], context, blocks)
    end

    def render_block(node, context, blocks)
      output << "Block(#{node.name})\n"
      render_children(node.children, context, blocks)
    end

    def render_parameters(parameters, context, blocks)
      return if parameters.none?

      output << '['

      parameters.each_with_index do |param, idx|
        render(param, context, blocks)
        output << ', ' unless idx == parameters.length - 1
      end

      output << ']'
    end

    def render_children(nodes, context, blocks)
      nodes.each_with_index do |node, idx|
        is_last_loop = idx == nodes.length - 1

        output << ("\u2503   " * @level)

        branch = is_last_loop ? "\u2516" : "\u2523"

        output << "#{branch}\u2501\u2501\u2501"

        @level += 1

        render(node, context, blocks)

        @level -= 1

        output << "\n" unless is_last_loop
      end
    end
  end
end