unparser 0.4.9 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -4
  3. data/bin/unparser +1 -1
  4. data/lib/unparser.rb +115 -61
  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 +26 -39
  9. data/lib/unparser/color.rb +0 -3
  10. data/lib/unparser/comments.rb +0 -26
  11. data/lib/unparser/constants.rb +4 -53
  12. data/lib/unparser/diff.rb +0 -17
  13. data/lib/unparser/dsl.rb +0 -32
  14. data/lib/unparser/emitter.rb +23 -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 +11 -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 +15 -71
  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 +46 -6
  75. data/lib/unparser/validation.rb +58 -35
  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 +66 -34
  88. data/lib/unparser/emitter/empty.rb +0 -23
  89. data/lib/unparser/emitter/ensure.rb +0 -37
  90. data/lib/unparser/emitter/literal.rb +0 -10
  91. data/lib/unparser/emitter/literal/array.rb +0 -29
  92. data/lib/unparser/emitter/literal/dynamic.rb +0 -53
  93. data/lib/unparser/emitter/literal/dynamic_body.rb +0 -132
  94. data/lib/unparser/emitter/literal/execute_string.rb +0 -38
  95. data/lib/unparser/emitter/literal/hash.rb +0 -156
  96. data/lib/unparser/emitter/literal/primitive.rb +0 -145
  97. data/lib/unparser/emitter/literal/range.rb +0 -36
  98. data/lib/unparser/emitter/literal/regexp.rb +0 -114
  99. data/lib/unparser/emitter/literal/singleton.rb +0 -26
  100. data/lib/unparser/emitter/meta.rb +0 -16
  101. data/lib/unparser/emitter/redo.rb +0 -25
  102. data/lib/unparser/emitter/resbody.rb +0 -76
  103. data/lib/unparser/emitter/retry.rb +0 -25
  104. data/lib/unparser/emitter/send/binary.rb +0 -57
  105. data/lib/unparser/emitter/send/conditional.rb +0 -40
  106. data/lib/unparser/emitter/send/regular.rb +0 -40
  107. data/lib/unparser/preprocessor.rb +0 -159
@@ -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,55 @@ 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
+ if
49
+ in_pattern
50
+ int
51
+ kwsplat
52
+ lambda
53
+ match_rest
54
+ pair
55
+ rescue
56
+ send
57
+ shadowarg
58
+ splat
59
+ str
60
+ sym
61
+ ].each do |type|
62
+ name = "n_#{type}?"
63
+ define_method(name) do |node|
64
+ n?(type, node)
65
+ end
66
+ private(name)
67
+ end
68
+
69
+ def unwrap_single_begin(node)
70
+ if n_begin?(node) && node.children.one?
71
+ node.children.first
72
+ else
73
+ node
74
+ end
75
+ end
36
76
  end # NodeHelpers
37
77
  end # Unparser
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Unparser
4
4
  # Validation of unparser results
5
- #
6
- # ignore :reek:TooManyMethods
7
5
  class Validation
8
6
  include Adamantium::Flat, Anima.new(
9
7
  :generated_node,
@@ -25,7 +23,7 @@ module Unparser
25
23
  original_node,
26
24
  generated_source,
27
25
  generated_node
28
- ].all?(&:right?) && generated_node.from_right.eql?(original_node.from_right)
26
+ ].all?(&:right?) && generated_node.from_right.==(original_node.from_right)
29
27
  end
30
28
 
31
29
  # Return error report
@@ -55,16 +53,14 @@ module Unparser
55
53
  def self.from_string(original_source)
56
54
  original_node = Unparser
57
55
  .parse_either(original_source)
58
- .fmap(&Preprocessor.method(:run))
59
56
 
60
57
  generated_source = original_node
61
58
  .lmap(&method(:const_unit))
62
- .bind(&method(:unparse_either))
59
+ .bind(&Unparser.method(:unparse_either))
63
60
 
64
61
  generated_node = generated_source
65
62
  .lmap(&method(:const_unit))
66
63
  .bind(&Unparser.method(:parse_either))
67
- .fmap(&Preprocessor.method(:run))
68
64
 
69
65
  new(
70
66
  identification: '(string)',
@@ -75,6 +71,27 @@ module Unparser
75
71
  )
76
72
  end
77
73
 
74
+ # Create validator from node
75
+ #
76
+ # @param [Parser::AST::Node] original_node
77
+ #
78
+ # @return [Validator]
79
+ def self.from_node(original_node)
80
+ generated_source = Unparser.unparse_either(original_node)
81
+
82
+ generated_node = generated_source
83
+ .lmap(&method(:const_unit))
84
+ .bind(&Unparser.public_method(:parse_either))
85
+
86
+ new(
87
+ identification: '(string)',
88
+ original_source: generated_source,
89
+ original_node: MPrelude::Either::Right.new(original_node),
90
+ generated_source: generated_source,
91
+ generated_node: generated_node
92
+ )
93
+ end
94
+
78
95
  # Create validator from file
79
96
  #
80
97
  # @param [Pathname] path
@@ -86,21 +103,10 @@ module Unparser
86
103
 
87
104
  private
88
105
 
89
- # Create a labeled report from
90
- #
91
- # @param [String] label
92
- # @param [Symbol] attribute_name
93
- #
94
- # @return [Array<String>]
95
106
  def make_report(label, attribute_name)
96
107
  ["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
97
108
  end
98
109
 
99
- # Report optional exception
100
- #
101
- # @param [Exception, nil] exception
102
- #
103
- # @return [Array<String>]
104
110
  def report_exception(exception)
105
111
  if exception
106
112
  [exception.inspect].concat(exception.backtrace.take(20))
@@ -109,9 +115,6 @@ module Unparser
109
115
  end
110
116
  end
111
117
 
112
- # Report the node diff
113
- #
114
- # @return [Array<String>]
115
118
  def node_diff_report
116
119
  diff = nil
117
120
 
@@ -127,23 +130,43 @@ module Unparser
127
130
  diff ? ['Node-Diff:', diff] : []
128
131
  end
129
132
 
130
- # Create unit represented as nil
131
- #
132
- # @param [Object] _value
133
- #
134
- # @return [nil]
135
133
  def self.const_unit(_value); end
136
134
  private_class_method :const_unit
137
135
 
138
- # Unparse capturing errors
139
- #
140
- # @param [Parser::AST::Node] node
141
- #
142
- # @return [Either<RuntimeError, String>]
143
- def self.unparse_either(node)
144
- MPrelude::Either
145
- .wrap_error(RuntimeError) { Unparser.unparse(node) }
146
- end
147
- private_class_method :unparse_either
136
+ class Literal < self
137
+ def success?
138
+ original_source.eql?(generated_source)
139
+ end
140
+
141
+ def report
142
+ message = [identification]
143
+
144
+ message.concat(make_report('Original-Source', :original_source))
145
+ message.concat(make_report('Generated-Source', :generated_source))
146
+ message.concat(make_report('Original-Node', :original_node))
147
+ message.concat(make_report('Generated-Node', :generated_node))
148
+ message.concat(node_diff_report)
149
+ message.concat(source_diff_report)
150
+
151
+ message.join("\n")
152
+ end
153
+
154
+ private
155
+
156
+ def source_diff_report
157
+ diff = nil
158
+
159
+ original_source.fmap do |original|
160
+ generated_source.fmap do |generated|
161
+ diff = Diff.new(
162
+ original.split("\n", -1),
163
+ generated.split("\n", -1)
164
+ ).colorized_diff
165
+ end
166
+ end
167
+
168
+ diff ? ['Source-Diff:', diff] : []
169
+ end
170
+ end # Literal
148
171
  end # Validation
149
172
  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