summaryrefslogtreecommitdiff
path: root/prism/templates/lib/prism/inspect_visitor.rb.erb
blob: 9328da636b0739c1eb3499a0ff46d524161c8931 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
module Prism
  # This visitor is responsible for composing the strings that get returned by
  # the various #inspect methods defined on each of the nodes.
  class InspectVisitor < Visitor
    # Most of the time, we can simply pass down the indent to the next node.
    # However, when we are inside a list we want some extra special formatting
    # when we hit an element in that list. In this case, we have a special
    # command that replaces the subsequent indent with the given value.
    class Replace # :nodoc:
      attr_reader :value

      def initialize(value)
        @value = value
      end
    end

    private_constant :Replace

    # The current prefix string.
    attr_reader :indent

    # The list of commands that we need to execute in order to compose the
    # final string.
    attr_reader :commands

    # Initializes a new instance of the InspectVisitor.
    def initialize(indent = +"")
      @indent = indent
      @commands = []
    end

    # Compose an inspect string for the given node.
    def self.compose(node)
      visitor = new
      node.accept(visitor)
      visitor.compose
    end

    # Compose the final string.
    def compose
      buffer = +""
      replace = nil

      until commands.empty?
        # @type var command: String | node | Replace
        # @type var indent: String
        command, indent = *commands.shift

        case command
        when String
          buffer << (replace || indent)
          buffer << command
          replace = nil
        when Node
          visitor = InspectVisitor.new(indent)
          command.accept(visitor)
          @commands = [*visitor.commands, *@commands]
        when Replace
          replace = command.value
        else
          raise "Unknown command: #{command.inspect}"
        end
      end

      buffer
    end
    <%- nodes.each do |node| -%>

    # Inspect a <%= node.name %> node.
    def visit_<%= node.human %>(node)
      commands << [inspect_node(<%= node.name.inspect %>, node), indent]
      <%- node.fields.each_with_index do |field, index| -%>
      <%- pointer = index == node.fields.length - 1 ? "└── " : "├── " -%>
      <%- preadd = index == node.fields.length - 1 ? "    " : "│   " -%>
      <%- case field -%>
      <%- when Prism::Template::NodeListField -%>
      commands << ["<%= pointer %><%= field.name %>: (length: #{(<%= field.name %> = node.<%= field.name %>).length})\n", indent]
      if <%= field.name %>.any?
        <%= field.name %>[0...-1].each do |child|
          commands << [Replace.new("#{indent}<%= preadd %>├── "), indent]
          commands << [child, "#{indent}<%= preadd %>│   "]
        end
        commands << [Replace.new("#{indent}<%= preadd %>└── "), indent]
        commands << [<%= field.name %>[-1], "#{indent}<%= preadd %>    "]
      end
      <%- when Prism::Template::NodeField -%>
      commands << ["<%= pointer %><%= field.name %>:\n", indent]
      commands << [node.<%= field.name %>, "#{indent}<%= preadd %>"]
      <%- when Prism::Template::OptionalNodeField -%>
      if (<%= field.name %> = node.<%= field.name %>).nil?
        commands << ["<%= pointer %><%= field.name %>: ∅\n", indent]
      else
        commands << ["<%= pointer %><%= field.name %>:\n", indent]
        commands << [<%= field.name %>, "#{indent}<%= preadd %>"]
      end
      <%- when Prism::Template::ConstantField, Prism::Template::ConstantListField, Prism::Template::StringField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::IntegerField, Prism::Template::DoubleField -%>
      commands << ["<%= pointer %><%= field.name %>: #{node.<%= field.name %>.inspect}\n", indent]
      <%- when Prism::Template::OptionalConstantField -%>
      if (<%= field.name %> = node.<%= field.name %>).nil?
        commands << ["<%= pointer %><%= field.name %>: ∅\n", indent]
      else
        commands << ["<%= pointer %><%= field.name %>: #{<%= field.name %>.inspect}\n", indent]
      end
      <%- when Prism::Template::FlagsField -%>
      <%- flag = flags.find { |flag| flag.name == field.kind }.tap { |flag| raise unless flag } -%>
      flags = [<%= flag.values.map { |value| "(\"#{value.name.downcase}\" if node.#{value.name.downcase}?)" }.join(", ") %>].compact
      commands << ["<%= pointer %><%= field.name %>: #{flags.empty? ? "∅" : flags.join(", ")}\n", indent]
      <%- when Prism::Template::LocationField, Prism::Template::OptionalLocationField -%>
      commands << ["<%= pointer %><%= field.name %>: #{inspect_location(node.<%= field.name %>)}\n", indent]
      <%- end -%>
      <%- end -%>
    end
    <%- end -%>

    private

    # Compose a header for the given node.
    def inspect_node(name, node)
      location = node.location
      "@ #{name} (location: (#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}))\n"
    end

    # Compose a string representing the given inner location field.
    def inspect_location(location)
      if location
        "(#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}) = #{location.slice.inspect}"
      else
        "∅"
      end
    end
  end
end