unparser 0.4.9 → 0.5.4
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.
- checksums.yaml +4 -4
- data/README.md +19 -4
- data/bin/unparser +1 -1
- data/lib/unparser.rb +115 -61
- data/lib/unparser/ast.rb +0 -1
- data/lib/unparser/ast/local_variable_scope.rb +6 -76
- data/lib/unparser/buffer.rb +19 -16
- data/lib/unparser/cli.rb +26 -39
- data/lib/unparser/color.rb +0 -3
- data/lib/unparser/comments.rb +0 -26
- data/lib/unparser/constants.rb +4 -53
- data/lib/unparser/diff.rb +0 -17
- data/lib/unparser/dsl.rb +0 -32
- data/lib/unparser/emitter.rb +23 -425
- data/lib/unparser/emitter/alias.rb +2 -8
- data/lib/unparser/emitter/args.rb +45 -0
- data/lib/unparser/emitter/argument.rb +8 -166
- data/lib/unparser/emitter/array.rb +27 -0
- data/lib/unparser/emitter/array_pattern.rb +29 -0
- data/lib/unparser/emitter/assignment.rb +36 -127
- data/lib/unparser/emitter/begin.rb +9 -84
- data/lib/unparser/emitter/binary.rb +7 -20
- data/lib/unparser/emitter/block.rb +57 -41
- data/lib/unparser/emitter/case.rb +6 -48
- data/lib/unparser/emitter/case_guard.rb +27 -0
- data/lib/unparser/emitter/case_match.rb +40 -0
- data/lib/unparser/emitter/cbase.rb +1 -3
- data/lib/unparser/emitter/class.rb +6 -26
- data/lib/unparser/emitter/const_pattern.rb +24 -0
- data/lib/unparser/emitter/def.rb +7 -51
- data/lib/unparser/emitter/defined.rb +2 -12
- data/lib/unparser/emitter/dstr.rb +22 -0
- data/lib/unparser/emitter/dsym.rb +41 -0
- data/lib/unparser/emitter/flipflop.rb +11 -10
- data/lib/unparser/emitter/float.rb +29 -0
- data/lib/unparser/emitter/flow_modifier.rb +11 -55
- data/lib/unparser/emitter/for.rb +5 -19
- data/lib/unparser/emitter/hash.rb +74 -0
- data/lib/unparser/emitter/hash_pattern.rb +67 -0
- data/lib/unparser/emitter/hookexe.rb +5 -11
- data/lib/unparser/emitter/if.rb +15 -71
- data/lib/unparser/emitter/in_match.rb +21 -0
- data/lib/unparser/emitter/in_pattern.rb +34 -0
- data/lib/unparser/emitter/index.rb +21 -88
- data/lib/unparser/emitter/kwbegin.rb +31 -0
- data/lib/unparser/emitter/lambda.rb +0 -8
- data/lib/unparser/emitter/masgn.rb +20 -0
- data/lib/unparser/emitter/match.rb +3 -17
- data/lib/unparser/emitter/match_alt.rb +23 -0
- data/lib/unparser/emitter/match_as.rb +21 -0
- data/lib/unparser/emitter/match_rest.rb +26 -0
- data/lib/unparser/emitter/match_var.rb +19 -0
- data/lib/unparser/emitter/mlhs.rb +40 -0
- data/lib/unparser/emitter/module.rb +3 -9
- data/lib/unparser/emitter/op_assign.rb +12 -27
- data/lib/unparser/emitter/pin.rb +19 -0
- data/lib/unparser/emitter/primitive.rb +93 -0
- data/lib/unparser/emitter/range.rb +35 -0
- data/lib/unparser/emitter/regexp.rb +35 -0
- data/lib/unparser/emitter/repetition.rb +17 -57
- data/lib/unparser/emitter/rescue.rb +1 -97
- data/lib/unparser/emitter/root.rb +17 -1
- data/lib/unparser/emitter/send.rb +10 -219
- data/lib/unparser/emitter/simple.rb +33 -0
- data/lib/unparser/emitter/splat.rb +2 -18
- data/lib/unparser/emitter/super.rb +1 -29
- data/lib/unparser/emitter/undef.rb +1 -9
- data/lib/unparser/emitter/variable.rb +1 -31
- data/lib/unparser/emitter/xstr.rb +72 -0
- data/lib/unparser/emitter/yield.rb +1 -9
- data/lib/unparser/generation.rb +250 -0
- data/lib/unparser/node_details.rb +21 -0
- data/lib/unparser/node_details/send.rb +62 -0
- data/lib/unparser/node_helpers.rb +46 -6
- data/lib/unparser/validation.rb +58 -35
- data/lib/unparser/writer.rb +15 -0
- data/lib/unparser/writer/binary.rb +99 -0
- data/lib/unparser/writer/dynamic_string.rb +233 -0
- data/lib/unparser/writer/resbody.rb +40 -0
- data/lib/unparser/writer/rescue.rb +39 -0
- data/lib/unparser/writer/send.rb +124 -0
- data/lib/unparser/{emitter → writer}/send/attribute_assignment.rb +11 -26
- data/lib/unparser/writer/send/binary.rb +27 -0
- data/lib/unparser/writer/send/conditional.rb +25 -0
- data/lib/unparser/writer/send/regular.rb +33 -0
- data/lib/unparser/{emitter → writer}/send/unary.rb +10 -17
- metadata +66 -34
- data/lib/unparser/emitter/empty.rb +0 -23
- data/lib/unparser/emitter/ensure.rb +0 -37
- data/lib/unparser/emitter/literal.rb +0 -10
- data/lib/unparser/emitter/literal/array.rb +0 -29
- data/lib/unparser/emitter/literal/dynamic.rb +0 -53
- data/lib/unparser/emitter/literal/dynamic_body.rb +0 -132
- data/lib/unparser/emitter/literal/execute_string.rb +0 -38
- data/lib/unparser/emitter/literal/hash.rb +0 -156
- data/lib/unparser/emitter/literal/primitive.rb +0 -145
- data/lib/unparser/emitter/literal/range.rb +0 -36
- data/lib/unparser/emitter/literal/regexp.rb +0 -114
- data/lib/unparser/emitter/literal/singleton.rb +0 -26
- data/lib/unparser/emitter/meta.rb +0 -16
- data/lib/unparser/emitter/redo.rb +0 -25
- data/lib/unparser/emitter/resbody.rb +0 -76
- data/lib/unparser/emitter/retry.rb +0 -25
- data/lib/unparser/emitter/send/binary.rb +0 -57
- data/lib/unparser/emitter/send/conditional.rb +0 -40
- data/lib/unparser/emitter/send/regular.rb +0 -40
- 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
|
data/lib/unparser/validation.rb
CHANGED
@@ -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
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|