unparser 0.4.6 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/bin/unparser +1 -1
  4. data/lib/unparser.rb +117 -62
  5. data/lib/unparser/ast.rb +0 -1
  6. data/lib/unparser/ast/local_variable_scope.rb +6 -76
  7. data/lib/unparser/buffer.rb +19 -16
  8. data/lib/unparser/cli.rb +84 -77
  9. data/lib/unparser/{cli/color.rb → color.rb} +0 -13
  10. data/lib/unparser/comments.rb +0 -26
  11. data/lib/unparser/constants.rb +4 -53
  12. data/lib/unparser/diff.rb +98 -0
  13. data/lib/unparser/dsl.rb +0 -32
  14. data/lib/unparser/emitter.rb +24 -425
  15. data/lib/unparser/emitter/alias.rb +2 -8
  16. data/lib/unparser/emitter/args.rb +45 -0
  17. data/lib/unparser/emitter/argument.rb +8 -166
  18. data/lib/unparser/emitter/array.rb +27 -0
  19. data/lib/unparser/emitter/array_pattern.rb +29 -0
  20. data/lib/unparser/emitter/assignment.rb +36 -127
  21. data/lib/unparser/emitter/begin.rb +9 -84
  22. data/lib/unparser/emitter/binary.rb +7 -20
  23. data/lib/unparser/emitter/block.rb +57 -41
  24. data/lib/unparser/emitter/case.rb +6 -48
  25. data/lib/unparser/emitter/case_guard.rb +27 -0
  26. data/lib/unparser/emitter/case_match.rb +40 -0
  27. data/lib/unparser/emitter/cbase.rb +1 -3
  28. data/lib/unparser/emitter/class.rb +6 -26
  29. data/lib/unparser/emitter/const_pattern.rb +24 -0
  30. data/lib/unparser/emitter/def.rb +7 -51
  31. data/lib/unparser/emitter/defined.rb +2 -12
  32. data/lib/unparser/emitter/dstr.rb +22 -0
  33. data/lib/unparser/emitter/dsym.rb +41 -0
  34. data/lib/unparser/emitter/flipflop.rb +11 -10
  35. data/lib/unparser/emitter/float.rb +29 -0
  36. data/lib/unparser/emitter/flow_modifier.rb +8 -55
  37. data/lib/unparser/emitter/for.rb +5 -19
  38. data/lib/unparser/emitter/hash.rb +74 -0
  39. data/lib/unparser/emitter/hash_pattern.rb +67 -0
  40. data/lib/unparser/emitter/hookexe.rb +5 -11
  41. data/lib/unparser/emitter/if.rb +9 -73
  42. data/lib/unparser/emitter/in_match.rb +21 -0
  43. data/lib/unparser/emitter/in_pattern.rb +34 -0
  44. data/lib/unparser/emitter/index.rb +21 -88
  45. data/lib/unparser/emitter/kwbegin.rb +31 -0
  46. data/lib/unparser/emitter/lambda.rb +0 -8
  47. data/lib/unparser/emitter/masgn.rb +20 -0
  48. data/lib/unparser/emitter/match.rb +3 -17
  49. data/lib/unparser/emitter/match_alt.rb +23 -0
  50. data/lib/unparser/emitter/match_as.rb +21 -0
  51. data/lib/unparser/emitter/match_rest.rb +26 -0
  52. data/lib/unparser/emitter/match_var.rb +19 -0
  53. data/lib/unparser/emitter/mlhs.rb +40 -0
  54. data/lib/unparser/emitter/module.rb +3 -9
  55. data/lib/unparser/emitter/op_assign.rb +12 -27
  56. data/lib/unparser/emitter/pin.rb +19 -0
  57. data/lib/unparser/emitter/primitive.rb +93 -0
  58. data/lib/unparser/emitter/range.rb +35 -0
  59. data/lib/unparser/emitter/regexp.rb +35 -0
  60. data/lib/unparser/emitter/repetition.rb +17 -57
  61. data/lib/unparser/emitter/rescue.rb +1 -97
  62. data/lib/unparser/emitter/root.rb +17 -1
  63. data/lib/unparser/emitter/send.rb +10 -219
  64. data/lib/unparser/emitter/simple.rb +33 -0
  65. data/lib/unparser/emitter/splat.rb +2 -18
  66. data/lib/unparser/emitter/super.rb +1 -29
  67. data/lib/unparser/emitter/undef.rb +1 -9
  68. data/lib/unparser/emitter/variable.rb +1 -31
  69. data/lib/unparser/emitter/xstr.rb +72 -0
  70. data/lib/unparser/emitter/yield.rb +1 -9
  71. data/lib/unparser/generation.rb +250 -0
  72. data/lib/unparser/node_details.rb +21 -0
  73. data/lib/unparser/node_details/send.rb +62 -0
  74. data/lib/unparser/node_helpers.rb +45 -6
  75. data/lib/unparser/validation.rb +151 -0
  76. data/lib/unparser/writer.rb +15 -0
  77. data/lib/unparser/writer/binary.rb +99 -0
  78. data/lib/unparser/writer/dynamic_string.rb +233 -0
  79. data/lib/unparser/writer/resbody.rb +40 -0
  80. data/lib/unparser/writer/rescue.rb +39 -0
  81. data/lib/unparser/writer/send.rb +124 -0
  82. data/lib/unparser/{emitter → writer}/send/attribute_assignment.rb +11 -26
  83. data/lib/unparser/writer/send/binary.rb +27 -0
  84. data/lib/unparser/writer/send/conditional.rb +25 -0
  85. data/lib/unparser/writer/send/regular.rb +33 -0
  86. data/lib/unparser/{emitter → writer}/send/unary.rb +10 -17
  87. metadata +151 -100
  88. data/.circleci/config.yml +0 -49
  89. data/.gitignore +0 -37
  90. data/.rspec +0 -4
  91. data/.rubocop.yml +0 -9
  92. data/Changelog.md +0 -150
  93. data/Gemfile +0 -11
  94. data/Gemfile.lock +0 -176
  95. data/LICENSE +0 -20
  96. data/Rakefile +0 -22
  97. data/config/devtools.yml +0 -2
  98. data/config/flay.yml +0 -3
  99. data/config/flog.yml +0 -2
  100. data/config/mutant.yml +0 -6
  101. data/config/reek.yml +0 -98
  102. data/config/rubocop.yml +0 -122
  103. data/config/yardstick.yml +0 -2
  104. data/lib/unparser/cli/differ.rb +0 -152
  105. data/lib/unparser/cli/source.rb +0 -267
  106. data/lib/unparser/emitter/empty.rb +0 -23
  107. data/lib/unparser/emitter/ensure.rb +0 -37
  108. data/lib/unparser/emitter/literal.rb +0 -10
  109. data/lib/unparser/emitter/literal/array.rb +0 -29
  110. data/lib/unparser/emitter/literal/dynamic.rb +0 -53
  111. data/lib/unparser/emitter/literal/dynamic_body.rb +0 -132
  112. data/lib/unparser/emitter/literal/execute_string.rb +0 -38
  113. data/lib/unparser/emitter/literal/hash.rb +0 -156
  114. data/lib/unparser/emitter/literal/primitive.rb +0 -145
  115. data/lib/unparser/emitter/literal/range.rb +0 -36
  116. data/lib/unparser/emitter/literal/regexp.rb +0 -114
  117. data/lib/unparser/emitter/literal/singleton.rb +0 -26
  118. data/lib/unparser/emitter/meta.rb +0 -16
  119. data/lib/unparser/emitter/redo.rb +0 -25
  120. data/lib/unparser/emitter/resbody.rb +0 -76
  121. data/lib/unparser/emitter/retry.rb +0 -25
  122. data/lib/unparser/emitter/send/binary.rb +0 -57
  123. data/lib/unparser/emitter/send/conditional.rb +0 -40
  124. data/lib/unparser/emitter/send/regular.rb +0 -40
  125. data/lib/unparser/preprocessor.rb +0 -159
  126. data/spec/integration/unparser/corpus_spec.rb +0 -111
  127. data/spec/integrations.yml +0 -92
  128. data/spec/spec_helper.rb +0 -20
  129. data/spec/unit/unparser/buffer/append_spec.rb +0 -24
  130. data/spec/unit/unparser/buffer/append_without_prefix_spec.rb +0 -23
  131. data/spec/unit/unparser/buffer/capture_content_spec.rb +0 -17
  132. data/spec/unit/unparser/buffer/content_spec.rb +0 -38
  133. data/spec/unit/unparser/buffer/fresh_line_spec.rb +0 -20
  134. data/spec/unit/unparser/buffer/indent_spec.rb +0 -20
  135. data/spec/unit/unparser/buffer/nl_spec.rb +0 -16
  136. data/spec/unit/unparser/buffer/unindent_spec.rb +0 -20
  137. data/spec/unit/unparser/comments/consume_spec.rb +0 -22
  138. data/spec/unit/unparser/comments/take_all_spec.rb +0 -19
  139. data/spec/unit/unparser/comments/take_before_spec.rb +0 -46
  140. data/spec/unit/unparser/comments/take_eol_comments_spec.rb +0 -32
  141. data/spec/unit/unparser/emitter/class_methods/handle_spec.rb +0 -17
  142. data/spec/unit/unparser_spec.rb +0 -1847
  143. data/unparser.gemspec +0 -30
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ module NodeDetails
5
+ include Constants, NodeHelpers
6
+
7
+ def self.included(descendant)
8
+ descendant.class_eval do
9
+ include Adamantium::Flat, Concord.new(:node)
10
+
11
+ extend DSL
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def children
18
+ node.children
19
+ end
20
+ end # NodeDetails
21
+ end # Unparser
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ module NodeDetails
5
+ class Send
6
+ include NodeDetails
7
+
8
+ ASSIGN_SUFFIX = '='.freeze
9
+ NON_ASSIGN_RANGE = (0..-2).freeze
10
+
11
+ private_constant(*constants(false))
12
+
13
+ children :receiver, :selector
14
+
15
+ public :receiver, :selector
16
+
17
+ def selector_binary_operator?
18
+ BINARY_OPERATORS.include?(selector)
19
+ end
20
+
21
+ def binary_syntax_allowed?
22
+ selector_binary_operator? && arguments.one? && !n_splat?(arguments.first)
23
+ end
24
+
25
+ def selector_unary_operator?
26
+ UNARY_OPERATORS.include?(selector)
27
+ end
28
+
29
+ def assignment_operator?
30
+ assignment? && !selector_binary_operator? && !selector_unary_operator?
31
+ end
32
+
33
+ def arguments?
34
+ arguments.any?
35
+ end
36
+
37
+ def non_assignment_selector
38
+ if assignment?
39
+ string_selector[NON_ASSIGN_RANGE]
40
+ else
41
+ string_selector
42
+ end
43
+ end
44
+
45
+ def assignment?
46
+ string_selector[-1].eql?(ASSIGN_SUFFIX)
47
+ end
48
+ memoize :assignment?
49
+
50
+ def arguments
51
+ children[2..-1]
52
+ end
53
+ memoize :arguments
54
+
55
+ def string_selector
56
+ selector.to_s
57
+ end
58
+ memoize :string_selector
59
+
60
+ end # Send
61
+ end # NodeDetails
62
+ end # Unparser
@@ -11,9 +11,6 @@ module Unparser
11
11
  # @return [Parser::AST::Node]
12
12
  #
13
13
  # @api private
14
- #
15
- # ignore :reek:UncommunicativeMethodName
16
- # ignore :reek:UtilityFunction
17
14
  def s(type, *children)
18
15
  Parser::AST::Node.new(type, children)
19
16
  end
@@ -26,12 +23,54 @@ module Unparser
26
23
  # @param [Array] children
27
24
  #
28
25
  # @api private
29
- #
30
- # ignore :reek:UncommunicativeMethodName
31
- # ignore :reek:UtilityFunction
32
26
  def n(type, children = [])
33
27
  Parser::AST::Node.new(type, children)
34
28
  end
35
29
 
30
+ def n?(type, node)
31
+ node.type.equal?(type)
32
+ end
33
+
34
+ %i[
35
+ arg
36
+ args
37
+ array
38
+ array_pattern
39
+ empty_else
40
+ begin
41
+ block
42
+ cbase
43
+ const
44
+ dstr
45
+ ensure
46
+ hash
47
+ hash_pattern
48
+ in_pattern
49
+ int
50
+ kwsplat
51
+ lambda
52
+ match_rest
53
+ pair
54
+ rescue
55
+ send
56
+ shadowarg
57
+ splat
58
+ str
59
+ sym
60
+ ].each do |type|
61
+ name = "n_#{type}?"
62
+ define_method(name) do |node|
63
+ n?(type, node)
64
+ end
65
+ private(name)
66
+ end
67
+
68
+ def unwrap_single_begin(node)
69
+ if n_begin?(node) && node.children.one?
70
+ node.children.first
71
+ else
72
+ node
73
+ end
74
+ end
36
75
  end # NodeHelpers
37
76
  end # Unparser
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ # Validation of unparser results
5
+ class Validation
6
+ include Adamantium::Flat, Anima.new(
7
+ :generated_node,
8
+ :generated_source,
9
+ :identification,
10
+ :original_node,
11
+ :original_source
12
+ )
13
+
14
+ # Test if source could be unparsed successfully
15
+ #
16
+ # @return [Boolean]
17
+ #
18
+ # @api private
19
+ #
20
+ def success?
21
+ [
22
+ original_source,
23
+ original_node,
24
+ generated_source,
25
+ generated_node
26
+ ].all?(&:right?) && generated_node.from_right.==(original_node.from_right)
27
+ end
28
+
29
+ # Return error report
30
+ #
31
+ # @return [String]
32
+ #
33
+ # @api private
34
+ #
35
+ def report
36
+ message = [identification]
37
+
38
+ message.concat(make_report('Original-Source', :original_source))
39
+ message.concat(make_report('Generated-Source', :generated_source))
40
+ message.concat(make_report('Original-Node', :original_node))
41
+ message.concat(make_report('Generated-Node', :generated_node))
42
+ message.concat(node_diff_report)
43
+
44
+ message.join("\n")
45
+ end
46
+ memoize :report
47
+
48
+ # Create validator from string
49
+ #
50
+ # @param [String] original_source
51
+ #
52
+ # @return [Validator]
53
+ def self.from_string(original_source)
54
+ original_node = Unparser
55
+ .parse_either(original_source)
56
+
57
+ generated_source = original_node
58
+ .lmap(&method(:const_unit))
59
+ .bind(&Unparser.method(:unparse_either))
60
+
61
+ generated_node = generated_source
62
+ .lmap(&method(:const_unit))
63
+ .bind(&Unparser.method(:parse_either))
64
+
65
+ new(
66
+ identification: '(string)',
67
+ original_source: MPrelude::Either::Right.new(original_source),
68
+ original_node: original_node,
69
+ generated_source: generated_source,
70
+ generated_node: generated_node
71
+ )
72
+ end
73
+
74
+ # Create validator from file
75
+ #
76
+ # @param [Pathname] path
77
+ #
78
+ # @return [Validator]
79
+ def self.from_path(path)
80
+ from_string(path.read).with(identification: path.to_s)
81
+ end
82
+
83
+ private
84
+
85
+ def make_report(label, attribute_name)
86
+ ["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
87
+ end
88
+
89
+ def report_exception(exception)
90
+ if exception
91
+ [exception.inspect].concat(exception.backtrace.take(20))
92
+ else
93
+ ['undefined']
94
+ end
95
+ end
96
+
97
+ def node_diff_report
98
+ diff = nil
99
+
100
+ original_node.fmap do |original|
101
+ generated_node.fmap do |generated|
102
+ diff = Diff.new(
103
+ original.to_s.lines.map(&:chomp),
104
+ generated.to_s.lines.map(&:chomp)
105
+ ).colorized_diff
106
+ end
107
+ end
108
+
109
+ diff ? ['Node-Diff:', diff] : []
110
+ end
111
+
112
+ def self.const_unit(_value); end
113
+ private_class_method :const_unit
114
+
115
+ class Literal < self
116
+ def success?
117
+ original_source.eql?(generated_source)
118
+ end
119
+
120
+ def report
121
+ message = [identification]
122
+
123
+ message.concat(make_report('Original-Source', :original_source))
124
+ message.concat(make_report('Generated-Source', :generated_source))
125
+ message.concat(make_report('Original-Node', :original_node))
126
+ message.concat(make_report('Generated-Node', :generated_node))
127
+ message.concat(node_diff_report)
128
+ message.concat(source_diff_report)
129
+
130
+ message.join("\n")
131
+ end
132
+
133
+ private
134
+
135
+ def source_diff_report
136
+ diff = nil
137
+
138
+ original_source.fmap do |original|
139
+ generated_source.fmap do |generated|
140
+ diff = Diff.new(
141
+ original.split("\n", -1),
142
+ generated.split("\n", -1)
143
+ ).colorized_diff
144
+ end
145
+ end
146
+
147
+ diff ? ['Source-Diff:', diff] : []
148
+ end
149
+ end # Literal
150
+ end # Validation
151
+ end # Unparser
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ module Writer
5
+ include Generation, NodeHelpers
6
+
7
+ def self.included(descendant)
8
+ descendant.class_eval do
9
+ include Anima.new(:buffer, :comments, :node, :local_variable_scope)
10
+
11
+ extend DSL
12
+ end
13
+ end
14
+ end # Writer
15
+ end # Unparser
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ module Writer
5
+ class Binary
6
+ include Writer, Adamantium::Flat
7
+
8
+ children :left, :right
9
+
10
+ OPERATOR_TOKENS =
11
+ {
12
+ and: '&&',
13
+ or: '||'
14
+ }.freeze
15
+
16
+ KEYWORD_TOKENS =
17
+ {
18
+ and: 'and',
19
+ or: 'or'
20
+ }.freeze
21
+
22
+ KEYWORD_SYMBOLS =
23
+ {
24
+ and: :kAND,
25
+ or: :kOR
26
+ }.freeze
27
+
28
+ OPERATOR_SYMBOLS =
29
+ {
30
+ and: :tANDOP,
31
+ or: :tOROP
32
+ }.freeze
33
+
34
+ MAP =
35
+ {
36
+ kAND: 'and',
37
+ kOR: 'or',
38
+ tOROP: '||',
39
+ tANDOP: '&&'
40
+ }.freeze
41
+
42
+ NEED_KEYWORD = %i[return break next].freeze
43
+
44
+ private_constant(*constants(false))
45
+
46
+ def emit_operator
47
+ emit_with(OPERATOR_TOKENS)
48
+ end
49
+
50
+ def symbol_name
51
+ true
52
+ end
53
+
54
+ def dispatch
55
+ left_emitter.write_to_buffer
56
+ write(' ', MAP.fetch(effective_symbol), ' ')
57
+ visit(right)
58
+ end
59
+
60
+ private
61
+
62
+ def effective_symbol
63
+ if NEED_KEYWORD.include?(right.type) || NEED_KEYWORD.include?(left.type)
64
+ return keyword_symbol
65
+ end
66
+
67
+ unless left_emitter.symbol_name
68
+ return operator_symbol
69
+ end
70
+
71
+ keyword_symbol
72
+ end
73
+
74
+ def emit_with(map)
75
+ visit(left)
76
+ write(' ', map.fetch(node.type), ' ')
77
+ visit(right)
78
+ end
79
+
80
+ def keyword_symbol
81
+ KEYWORD_SYMBOLS.fetch(node.type)
82
+ end
83
+
84
+ def operator_symbol
85
+ OPERATOR_SYMBOLS.fetch(node.type)
86
+ end
87
+
88
+ def left_emitter
89
+ emitter(left)
90
+ end
91
+ memoize :left_emitter
92
+
93
+ def right_emitter
94
+ emitter(right)
95
+ end
96
+ memoize :right_emitter
97
+ end # Binary
98
+ end # Writer
99
+ end # Unparser
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ module Writer
5
+ class DynamicString
6
+ include Writer, Adamantium::Flat
7
+
8
+ PATTERNS_2 = IceNine.deep_freeze(
9
+ [
10
+ %i[str_empty begin],
11
+ %i[begin str_nl]
12
+ ]
13
+ )
14
+
15
+ PATTERNS_3 = IceNine.deep_freeze(
16
+ [
17
+ %i[begin str_nl_eol str_nl_eol],
18
+ %i[str_nl_eol begin str_nl_eol],
19
+ %i[str_ws begin str_nl_eol]
20
+ ]
21
+ )
22
+
23
+ FLAT_INTERPOLATION = %i[ivar cvar gvar].to_set.freeze
24
+
25
+ private_constant(*constants(false))
26
+
27
+ def emit_heredoc_reminder
28
+ return unless heredoc?
29
+
30
+ emit_heredoc_body
31
+ emit_heredoc_footer
32
+ end
33
+
34
+ def dispatch
35
+ if heredoc?
36
+ emit_heredoc_header
37
+ else
38
+ emit_dstr
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def heredoc_header
45
+ need_squiggly? ? '<<~HEREDOC' : '<<-HEREDOC'
46
+ end
47
+
48
+ def heredoc?
49
+ !children.empty? && (nl_last_child? && heredoc_pattern?)
50
+ end
51
+
52
+ def emit_heredoc_header
53
+ write(heredoc_header)
54
+ end
55
+
56
+ def emit_heredoc_body
57
+ nl
58
+ if need_squiggly?
59
+ emit_squiggly_heredoc_body
60
+ else
61
+ emit_normal_heredoc_body
62
+ end
63
+ end
64
+
65
+ def emit_heredoc_footer
66
+ write('HEREDOC')
67
+ end
68
+
69
+ def classify(node)
70
+ if n_str?(node)
71
+ classify_str(node)
72
+ else
73
+ node.type
74
+ end
75
+ end
76
+
77
+ def classify_str(node)
78
+ if str_nl?(node)
79
+ :str_nl
80
+ elsif node.children.first.end_with?("\n")
81
+ :str_nl_eol
82
+ elsif str_ws?(node)
83
+ :str_ws
84
+ elsif str_empty?(node)
85
+ :str_empty
86
+ end
87
+ end
88
+
89
+ def str_nl?(node)
90
+ node.eql?(s(:str, "\n"))
91
+ end
92
+
93
+ def str_empty?(node)
94
+ node.eql?(s(:str, ''))
95
+ end
96
+
97
+ def str_ws?(node)
98
+ /\A( |\t)+\z/.match?(node.children.first)
99
+ end
100
+
101
+ def heredoc_pattern?
102
+ heredoc_pattern_2? || heredoc_pattern_3?
103
+ end
104
+
105
+ def heredoc_pattern_3?
106
+ children.each_cons(3).any? do |group|
107
+ PATTERNS_3.include?(group.map(&method(:classify)))
108
+ end
109
+ end
110
+
111
+ def heredoc_pattern_2?
112
+ children.each_cons(2).any? do |group|
113
+ PATTERNS_2.include?(group.map(&method(:classify)))
114
+ end
115
+ end
116
+
117
+ def nl_last_child?
118
+ last = children.last
119
+ n_str?(last) && last.children.first[-1].eql?("\n")
120
+ end
121
+
122
+ def need_squiggly?
123
+ children.any?(s(:str, ''))
124
+ end
125
+
126
+ def emit_squiggly_heredoc_body
127
+ buffer.indent
128
+ children.each do |child|
129
+ if n_str?(child)
130
+ write(escape_dynamic(child.children.first))
131
+ else
132
+ emit_dynamic(child)
133
+ end
134
+ end
135
+ buffer.unindent
136
+ end
137
+
138
+ def emit_normal_heredoc_body
139
+ buffer.root_indent do
140
+ children.each do |child|
141
+ if n_str?(child)
142
+ write(escape_dynamic(child.children.first))
143
+ else
144
+ emit_dynamic(child)
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ def escape_dynamic(string)
151
+ string.gsub('#', '\#')
152
+ end
153
+
154
+ def emit_dynamic(child)
155
+ if FLAT_INTERPOLATION.include?(child.type)
156
+ write('#')
157
+ visit(child)
158
+ elsif n_dstr?(child)
159
+ emit_body(child.children)
160
+ else
161
+ write('#{')
162
+ emit_dynamic_component(child.children.first)
163
+ write('}')
164
+ end
165
+ end
166
+
167
+ def emit_dynamic_component(node)
168
+ visit(node) if node
169
+ end
170
+
171
+ def emit_dstr
172
+ if children.empty?
173
+ write('%()')
174
+ else
175
+ segments.each_with_index do |children, index|
176
+ emit_segment(children, index)
177
+ end
178
+ end
179
+ end
180
+
181
+ def breakpoint?(child, current)
182
+ last_type = current.last&.type
183
+
184
+ [
185
+ n_str?(child) && last_type.equal?(:str) && current.none?(&method(:n_begin?)),
186
+ last_type.equal?(:dstr),
187
+ n_dstr?(child) && last_type
188
+ ].any?
189
+ end
190
+
191
+ def segments
192
+ segments = []
193
+
194
+ segments << current = []
195
+
196
+ children.each do |child|
197
+ if breakpoint?(child, current)
198
+ segments << current = []
199
+ end
200
+
201
+ current << child
202
+ end
203
+
204
+ segments
205
+ end
206
+
207
+ def emit_segment(children, index)
208
+ write(' ') unless index.zero?
209
+
210
+ write('"')
211
+ emit_body(children)
212
+ write('"')
213
+ end
214
+
215
+ def emit_body(children)
216
+ buffer.root_indent do
217
+ children.each_with_index do |child, index|
218
+ if n_str?(child)
219
+ string = child.children.first
220
+ if string.eql?("\n") && children.fetch(index.pred).type.equal?(:begin)
221
+ write("\n")
222
+ else
223
+ write(string.inspect[1..-2])
224
+ end
225
+ else
226
+ emit_dynamic(child)
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end # DynamicString
232
+ end # Writer
233
+ end # Unparser