File: dynamic_string.rb

package info (click to toggle)
ruby-unparser 0.6.13-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 936 kB
  • sloc: ruby: 7,691; sh: 6; makefile: 4
file content (211 lines) | stat: -rw-r--r-- 4,558 bytes parent folder | download
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# frozen_string_literal: true

module Unparser
  module Writer
    class DynamicString
      include Writer, Adamantium

      PATTERNS_2 =
        [
          %i[str_empty begin].freeze,
          %i[begin str_nl].freeze
        ].freeze

      PATTERNS_3 =
        [
          %i[begin str_nl_eol str_nl_eol].freeze,
          %i[str_nl_eol begin str_nl_eol].freeze,
          %i[str_ws begin str_nl_eol].freeze
        ].freeze

      FLAT_INTERPOLATION = %i[ivar cvar gvar nth_ref].to_set.freeze

      private_constant(*constants(false))

      def emit_heredoc_reminder
        return unless heredoc?

        emit_heredoc_body
        emit_heredoc_footer
      end

      def dispatch
        if heredoc?
          emit_heredoc_header
        else
          emit_dstr
        end
      end

    private

      def heredoc_header
        '<<-HEREDOC'
      end

      def heredoc?
        !children.empty? && (nl_last_child? && heredoc_pattern?)
      end

      def emit_heredoc_header
        write(heredoc_header)
      end

      def emit_heredoc_body
        nl
        emit_normal_heredoc_body
      end

      def emit_heredoc_footer
        write('HEREDOC')
      end

      def classify(node)
        if n_str?(node)
          classify_str(node)
        else
          node.type
        end
      end

      def classify_str(node)
        if str_nl?(node)
          :str_nl
        elsif node.children.first.end_with?("\n")
          :str_nl_eol
        elsif str_ws?(node)
          :str_ws
        elsif str_empty?(node)
          :str_empty
        end
      end

      def str_nl?(node)
        node.eql?(s(:str, "\n"))
      end

      def str_empty?(node)
        node.eql?(s(:str, ''))
      end

      def str_ws?(node)
        /\A( |\t)+\z/.match?(node.children.first)
      end

      def heredoc_pattern?
        heredoc_pattern_2? || heredoc_pattern_3?
      end

      def heredoc_pattern_3?
        children.each_cons(3).any? do |group|
          PATTERNS_3.include?(group.map(&method(:classify)))
        end
      end

      def heredoc_pattern_2?
        children.each_cons(2).any? do |group|
          PATTERNS_2.include?(group.map(&method(:classify)))
        end
      end

      def nl_last_child?
        last = children.last
        n_str?(last) && last.children.first[-1].eql?("\n")
      end

      def emit_normal_heredoc_body
        buffer.root_indent do
          children.each do |child|
            if n_str?(child)
              write(escape_dynamic(child.children.first))
            else
              emit_dynamic(child)
            end
          end
        end
      end

      def escape_dynamic(string)
        string.gsub('#', '\#')
      end

      def emit_dynamic(child)
        if FLAT_INTERPOLATION.include?(child.type)
          write('#')
          visit(child)
        elsif n_dstr?(child)
          emit_body(child.children)
        else
          write('#{')
          emit_dynamic_component(child.children.first)
          write('}')
        end
      end

      def emit_dynamic_component(node)
        visit(node) if node
      end

      def emit_dstr
        if children.empty?
          write('%()')
        else
          segments.each_with_index do |children, index|
            emit_segment(children, index)
          end
        end
      end

      def breakpoint?(child, current)
        last_type = current.last&.type

        [
          n_str?(child) && last_type.equal?(:str) && current.none?(&method(:n_begin?)),
          last_type.equal?(:dstr),
          n_dstr?(child) && last_type
        ].any?
      end

      def segments
        segments = []

        segments << current = []

        children.each do |child|
          if breakpoint?(child, current)
            segments << current = []
          end

          current << child
        end

        segments
      end

      def emit_segment(children, index)
        write(' ') unless index.zero?

        write('"')
        emit_body(children)
        write('"')
      end

      def emit_body(children)
        buffer.root_indent do
          children.each_with_index do |child, index|
            if n_str?(child)
              string = child.children.first
              if string.eql?("\n") && children.fetch(index.pred).type.equal?(:begin)
                write("\n")
              else
                write(string.inspect[1..-2])
              end
            else
              emit_dynamic(child)
            end
          end
        end
      end
    end # DynamicString
  end # Writer
end # Unparser