unparser 0.4.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/bin/unparser +1 -1
  3. data/lib/unparser.rb +98 -61
  4. data/lib/unparser/ast.rb +0 -1
  5. data/lib/unparser/ast/local_variable_scope.rb +6 -76
  6. data/lib/unparser/buffer.rb +19 -16
  7. data/lib/unparser/cli.rb +26 -39
  8. data/lib/unparser/color.rb +0 -3
  9. data/lib/unparser/comments.rb +0 -26
  10. data/lib/unparser/constants.rb +4 -53
  11. data/lib/unparser/diff.rb +0 -17
  12. data/lib/unparser/dsl.rb +0 -32
  13. data/lib/unparser/emitter.rb +23 -425
  14. data/lib/unparser/emitter/alias.rb +2 -8
  15. data/lib/unparser/emitter/args.rb +45 -0
  16. data/lib/unparser/emitter/argument.rb +8 -166
  17. data/lib/unparser/emitter/array.rb +27 -0
  18. data/lib/unparser/emitter/array_pattern.rb +29 -0
  19. data/lib/unparser/emitter/assignment.rb +36 -127
  20. data/lib/unparser/emitter/begin.rb +9 -84
  21. data/lib/unparser/emitter/binary.rb +7 -20
  22. data/lib/unparser/emitter/block.rb +57 -41
  23. data/lib/unparser/emitter/case.rb +6 -48
  24. data/lib/unparser/emitter/case_guard.rb +27 -0
  25. data/lib/unparser/emitter/case_match.rb +40 -0
  26. data/lib/unparser/emitter/cbase.rb +1 -3
  27. data/lib/unparser/emitter/class.rb +6 -26
  28. data/lib/unparser/emitter/const_pattern.rb +24 -0
  29. data/lib/unparser/emitter/def.rb +7 -51
  30. data/lib/unparser/emitter/defined.rb +2 -12
  31. data/lib/unparser/emitter/dstr.rb +22 -0
  32. data/lib/unparser/emitter/dsym.rb +41 -0
  33. data/lib/unparser/emitter/flipflop.rb +11 -10
  34. data/lib/unparser/emitter/float.rb +29 -0
  35. data/lib/unparser/emitter/flow_modifier.rb +8 -55
  36. data/lib/unparser/emitter/for.rb +5 -19
  37. data/lib/unparser/emitter/hash.rb +74 -0
  38. data/lib/unparser/emitter/hash_pattern.rb +67 -0
  39. data/lib/unparser/emitter/hookexe.rb +5 -11
  40. data/lib/unparser/emitter/if.rb +9 -73
  41. data/lib/unparser/emitter/in_match.rb +21 -0
  42. data/lib/unparser/emitter/in_pattern.rb +34 -0
  43. data/lib/unparser/emitter/index.rb +21 -88
  44. data/lib/unparser/emitter/kwbegin.rb +31 -0
  45. data/lib/unparser/emitter/lambda.rb +0 -8
  46. data/lib/unparser/emitter/masgn.rb +20 -0
  47. data/lib/unparser/emitter/match.rb +3 -17
  48. data/lib/unparser/emitter/match_alt.rb +23 -0
  49. data/lib/unparser/emitter/match_as.rb +21 -0
  50. data/lib/unparser/emitter/match_rest.rb +26 -0
  51. data/lib/unparser/emitter/match_var.rb +19 -0
  52. data/lib/unparser/emitter/mlhs.rb +40 -0
  53. data/lib/unparser/emitter/module.rb +3 -9
  54. data/lib/unparser/emitter/op_assign.rb +12 -27
  55. data/lib/unparser/emitter/pin.rb +19 -0
  56. data/lib/unparser/emitter/primitive.rb +93 -0
  57. data/lib/unparser/emitter/range.rb +35 -0
  58. data/lib/unparser/emitter/regexp.rb +35 -0
  59. data/lib/unparser/emitter/repetition.rb +17 -57
  60. data/lib/unparser/emitter/rescue.rb +1 -97
  61. data/lib/unparser/emitter/root.rb +17 -1
  62. data/lib/unparser/emitter/send.rb +10 -219
  63. data/lib/unparser/emitter/simple.rb +33 -0
  64. data/lib/unparser/emitter/splat.rb +2 -18
  65. data/lib/unparser/emitter/super.rb +1 -29
  66. data/lib/unparser/emitter/undef.rb +1 -9
  67. data/lib/unparser/emitter/variable.rb +1 -31
  68. data/lib/unparser/emitter/xstr.rb +72 -0
  69. data/lib/unparser/emitter/yield.rb +1 -9
  70. data/lib/unparser/generation.rb +250 -0
  71. data/lib/unparser/node_details.rb +21 -0
  72. data/lib/unparser/node_details/send.rb +62 -0
  73. data/lib/unparser/node_helpers.rb +45 -6
  74. data/lib/unparser/validation.rb +37 -35
  75. data/lib/unparser/writer.rb +15 -0
  76. data/lib/unparser/writer/binary.rb +99 -0
  77. data/lib/unparser/writer/dynamic_string.rb +229 -0
  78. data/lib/unparser/writer/resbody.rb +40 -0
  79. data/lib/unparser/writer/rescue.rb +39 -0
  80. data/lib/unparser/writer/send.rb +124 -0
  81. data/lib/unparser/{emitter → writer}/send/attribute_assignment.rb +11 -26
  82. data/lib/unparser/writer/send/binary.rb +27 -0
  83. data/lib/unparser/writer/send/conditional.rb +25 -0
  84. data/lib/unparser/writer/send/regular.rb +33 -0
  85. data/lib/unparser/{emitter → writer}/send/unary.rb +10 -17
  86. metadata +63 -31
  87. data/lib/unparser/emitter/empty.rb +0 -23
  88. data/lib/unparser/emitter/ensure.rb +0 -37
  89. data/lib/unparser/emitter/literal.rb +0 -10
  90. data/lib/unparser/emitter/literal/array.rb +0 -29
  91. data/lib/unparser/emitter/literal/dynamic.rb +0 -53
  92. data/lib/unparser/emitter/literal/dynamic_body.rb +0 -132
  93. data/lib/unparser/emitter/literal/execute_string.rb +0 -38
  94. data/lib/unparser/emitter/literal/hash.rb +0 -156
  95. data/lib/unparser/emitter/literal/primitive.rb +0 -145
  96. data/lib/unparser/emitter/literal/range.rb +0 -36
  97. data/lib/unparser/emitter/literal/regexp.rb +0 -114
  98. data/lib/unparser/emitter/literal/singleton.rb +0 -26
  99. data/lib/unparser/emitter/meta.rb +0 -16
  100. data/lib/unparser/emitter/redo.rb +0 -25
  101. data/lib/unparser/emitter/resbody.rb +0 -76
  102. data/lib/unparser/emitter/retry.rb +0 -25
  103. data/lib/unparser/emitter/send/binary.rb +0 -57
  104. data/lib/unparser/emitter/send/conditional.rb +0 -40
  105. data/lib/unparser/emitter/send/regular.rb +0 -40
  106. 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,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
@@ -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)',
@@ -86,21 +82,10 @@ module Unparser
86
82
 
87
83
  private
88
84
 
89
- # Create a labeled report from
90
- #
91
- # @param [String] label
92
- # @param [Symbol] attribute_name
93
- #
94
- # @return [Array<String>]
95
85
  def make_report(label, attribute_name)
96
86
  ["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
97
87
  end
98
88
 
99
- # Report optional exception
100
- #
101
- # @param [Exception, nil] exception
102
- #
103
- # @return [Array<String>]
104
89
  def report_exception(exception)
105
90
  if exception
106
91
  [exception.inspect].concat(exception.backtrace.take(20))
@@ -109,9 +94,6 @@ module Unparser
109
94
  end
110
95
  end
111
96
 
112
- # Report the node diff
113
- #
114
- # @return [Array<String>]
115
97
  def node_diff_report
116
98
  diff = nil
117
99
 
@@ -127,23 +109,43 @@ module Unparser
127
109
  diff ? ['Node-Diff:', diff] : []
128
110
  end
129
111
 
130
- # Create unit represented as nil
131
- #
132
- # @param [Object] _value
133
- #
134
- # @return [nil]
135
112
  def self.const_unit(_value); end
136
113
  private_class_method :const_unit
137
114
 
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
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
148
150
  end # Validation
149
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,229 @@
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
+ segments.each_with_index do |children, index|
173
+ emit_segment(children, index)
174
+ end
175
+ end
176
+
177
+ def breakpoint?(child, current)
178
+ last_type = current.last&.type
179
+
180
+ [
181
+ n_str?(child) && last_type.equal?(:str) && current.none?(&method(:n_begin?)),
182
+ last_type.equal?(:dstr),
183
+ n_dstr?(child) && last_type
184
+ ].any?
185
+ end
186
+
187
+ def segments
188
+ segments = []
189
+
190
+ segments << current = []
191
+
192
+ children.each do |child|
193
+ if breakpoint?(child, current)
194
+ segments << current = []
195
+ end
196
+
197
+ current << child
198
+ end
199
+
200
+ segments
201
+ end
202
+
203
+ def emit_segment(children, index)
204
+ write(' ') unless index.zero?
205
+
206
+ write('"')
207
+ emit_body(children)
208
+ write('"')
209
+ end
210
+
211
+ def emit_body(children)
212
+ buffer.root_indent do
213
+ children.each_with_index do |child, index|
214
+ if n_str?(child)
215
+ string = child.children.first
216
+ if string.eql?("\n") && children.fetch(index.pred).type.equal?(:begin)
217
+ write("\n")
218
+ else
219
+ write(string.inspect[1..-2])
220
+ end
221
+ else
222
+ emit_dynamic(child)
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end # DynamicString
228
+ end # Writer
229
+ end # Unparser