unparser 0.6.7 → 0.9.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.
- checksums.yaml +4 -4
- data/README.md +10 -10
- data/lib/unparser/adamantium.rb +3 -1
- data/lib/unparser/anima.rb +12 -3
- data/lib/unparser/ast/local_variable_scope.rb +29 -26
- data/lib/unparser/ast.rb +32 -49
- data/lib/unparser/buffer.rb +44 -2
- data/lib/unparser/cli.rb +48 -11
- data/lib/unparser/color.rb +9 -1
- data/lib/unparser/comments.rb +2 -2
- data/lib/unparser/diff.rb +8 -1
- data/lib/unparser/either.rb +13 -11
- data/lib/unparser/emitter/args.rb +5 -1
- data/lib/unparser/emitter/array.rb +0 -4
- data/lib/unparser/emitter/array_pattern.rb +1 -9
- data/lib/unparser/emitter/assignment.rb +17 -8
- data/lib/unparser/emitter/begin.rb +0 -6
- data/lib/unparser/emitter/binary.rb +1 -1
- data/lib/unparser/emitter/block.rb +18 -7
- data/lib/unparser/emitter/def.rb +1 -1
- data/lib/unparser/emitter/dstr.rb +6 -5
- data/lib/unparser/emitter/dsym.rb +1 -1
- data/lib/unparser/emitter/ensure.rb +16 -0
- data/lib/unparser/emitter/flipflop.rb +7 -2
- data/lib/unparser/emitter/flow_modifier.rb +1 -7
- data/lib/unparser/emitter/for.rb +1 -1
- data/lib/unparser/emitter/hash.rb +0 -8
- data/lib/unparser/emitter/hash_pattern.rb +1 -1
- data/lib/unparser/emitter/in_pattern.rb +9 -1
- data/lib/unparser/emitter/index.rb +0 -4
- data/lib/unparser/emitter/kwbegin.rb +1 -1
- data/lib/unparser/emitter/match_pattern.rb +7 -11
- data/lib/unparser/emitter/match_pattern_p.rb +6 -1
- data/lib/unparser/emitter/mlhs.rb +7 -1
- data/lib/unparser/emitter/op_assign.rb +0 -5
- data/lib/unparser/emitter/pair.rb +31 -5
- data/lib/unparser/emitter/primitive.rb +4 -6
- data/lib/unparser/emitter/range.rb +23 -2
- data/lib/unparser/emitter/regexp.rb +5 -17
- data/lib/unparser/emitter/rescue.rb +7 -1
- data/lib/unparser/emitter/root.rb +2 -9
- data/lib/unparser/emitter/send.rb +1 -5
- data/lib/unparser/emitter/string.rb +31 -0
- data/lib/unparser/emitter/xstr.rb +8 -1
- data/lib/unparser/emitter.rb +11 -11
- data/lib/unparser/equalizer.rb +105 -71
- data/lib/unparser/generation.rb +33 -29
- data/lib/unparser/node_details/send.rb +1 -0
- data/lib/unparser/node_details.rb +8 -1
- data/lib/unparser/node_helpers.rb +19 -9
- data/lib/unparser/util.rb +24 -0
- data/lib/unparser/validation.rb +70 -28
- data/lib/unparser/writer/array.rb +51 -0
- data/lib/unparser/writer/binary.rb +8 -4
- data/lib/unparser/writer/dynamic_string.rb +130 -138
- data/lib/unparser/writer/regexp.rb +101 -0
- data/lib/unparser/writer/resbody.rb +37 -3
- data/lib/unparser/writer/rescue.rb +3 -7
- data/lib/unparser/writer/send/unary.rb +9 -4
- data/lib/unparser/writer/send.rb +8 -14
- data/lib/unparser/writer.rb +31 -1
- data/lib/unparser.rb +157 -41
- metadata +82 -33
- data/lib/unparser/concord.rb +0 -114
data/lib/unparser/validation.rb
CHANGED
|
@@ -7,31 +7,38 @@ module Unparser
|
|
|
7
7
|
:generated_node,
|
|
8
8
|
:generated_source,
|
|
9
9
|
:identification,
|
|
10
|
-
:
|
|
10
|
+
:original_ast,
|
|
11
11
|
:original_source
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
class PhaseException
|
|
15
|
+
include Anima.new(:exception, :phase)
|
|
16
|
+
end
|
|
17
|
+
|
|
14
18
|
# Test if source could be unparsed successfully
|
|
15
19
|
#
|
|
16
20
|
# @return [Boolean]
|
|
17
21
|
#
|
|
18
22
|
# @api private
|
|
19
23
|
#
|
|
24
|
+
# rubocop:disable Style/OperatorMethodCall
|
|
25
|
+
# mutant:disable
|
|
20
26
|
def success?
|
|
21
27
|
[
|
|
22
28
|
original_source,
|
|
23
|
-
|
|
29
|
+
original_ast,
|
|
24
30
|
generated_source,
|
|
25
31
|
generated_node
|
|
26
32
|
].all?(&:right?) && generated_node.from_right.==(original_node.from_right)
|
|
27
33
|
end
|
|
34
|
+
# rubocop:enable Style/OperatorMethodCall
|
|
28
35
|
|
|
29
36
|
# Return error report
|
|
30
37
|
#
|
|
31
38
|
# @return [String]
|
|
32
39
|
#
|
|
33
40
|
# @api private
|
|
34
|
-
#
|
|
41
|
+
# mutant:disable
|
|
35
42
|
def report
|
|
36
43
|
message = [identification]
|
|
37
44
|
|
|
@@ -45,48 +52,57 @@ module Unparser
|
|
|
45
52
|
end
|
|
46
53
|
memoize :report
|
|
47
54
|
|
|
55
|
+
# mutant:disable
|
|
56
|
+
def original_node
|
|
57
|
+
original_ast.fmap(&:node)
|
|
58
|
+
end
|
|
59
|
+
|
|
48
60
|
# Create validator from string
|
|
49
61
|
#
|
|
50
62
|
# @param [String] original_source
|
|
51
63
|
#
|
|
52
64
|
# @return [Validator]
|
|
65
|
+
# mutant:disable
|
|
53
66
|
def self.from_string(original_source)
|
|
54
|
-
|
|
55
|
-
.parse_either(original_source)
|
|
67
|
+
original_ast = parse_ast_either(original_source)
|
|
56
68
|
|
|
57
|
-
generated_source =
|
|
69
|
+
generated_source = original_ast
|
|
58
70
|
.lmap(&method(:const_unit))
|
|
59
|
-
.bind(&
|
|
71
|
+
.bind(&method(:unparse_ast_either))
|
|
60
72
|
|
|
61
73
|
generated_node = generated_source
|
|
62
74
|
.lmap(&method(:const_unit))
|
|
63
|
-
.bind(&
|
|
75
|
+
.bind(&method(:parse_ast_either))
|
|
76
|
+
.fmap(&:node)
|
|
64
77
|
|
|
65
78
|
new(
|
|
66
|
-
|
|
67
|
-
original_source: Either::Right.new(original_source),
|
|
68
|
-
original_node: original_node,
|
|
79
|
+
generated_node: generated_node,
|
|
69
80
|
generated_source: generated_source,
|
|
70
|
-
|
|
81
|
+
identification: '(string)',
|
|
82
|
+
original_ast: original_ast,
|
|
83
|
+
original_source: Either::Right.new(original_source)
|
|
71
84
|
)
|
|
72
85
|
end
|
|
73
86
|
|
|
74
|
-
# Create validator from
|
|
87
|
+
# Create validator from ast
|
|
75
88
|
#
|
|
76
|
-
# @param [
|
|
89
|
+
# @param [Unparser::AST] ast
|
|
77
90
|
#
|
|
78
91
|
# @return [Validator]
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
#
|
|
93
|
+
# mutant:disable
|
|
94
|
+
def self.from_ast(ast:)
|
|
95
|
+
generated_source = Unparser.unparse_ast_either(ast)
|
|
81
96
|
|
|
82
97
|
generated_node = generated_source
|
|
83
98
|
.lmap(&method(:const_unit))
|
|
84
|
-
.bind(&
|
|
99
|
+
.bind(&method(:parse_ast_either))
|
|
100
|
+
.fmap(&:node)
|
|
85
101
|
|
|
86
102
|
new(
|
|
87
103
|
identification: '(string)',
|
|
88
104
|
original_source: generated_source,
|
|
89
|
-
|
|
105
|
+
original_ast: Either::Right.new(ast),
|
|
90
106
|
generated_source: generated_source,
|
|
91
107
|
generated_node: generated_node
|
|
92
108
|
)
|
|
@@ -97,24 +113,45 @@ module Unparser
|
|
|
97
113
|
# @param [Pathname] path
|
|
98
114
|
#
|
|
99
115
|
# @return [Validator]
|
|
116
|
+
#
|
|
117
|
+
# mutant:disable
|
|
100
118
|
def self.from_path(path)
|
|
101
|
-
from_string(path.read).with(identification: path.to_s)
|
|
119
|
+
from_string(path.read.freeze).with(identification: path.to_s)
|
|
102
120
|
end
|
|
103
121
|
|
|
122
|
+
# mutant:disable
|
|
123
|
+
def self.unparse_ast_either(ast)
|
|
124
|
+
Unparser.unparse_ast_either(ast)
|
|
125
|
+
end
|
|
126
|
+
private_class_method :unparse_ast_either
|
|
127
|
+
|
|
128
|
+
# mutant:disable
|
|
129
|
+
def self.parse_ast_either(source)
|
|
130
|
+
Unparser.parse_ast_either(source)
|
|
131
|
+
end
|
|
132
|
+
private_class_method :parse_ast_either
|
|
133
|
+
|
|
134
|
+
# mutant:disable
|
|
135
|
+
def self.const_unit(_); end
|
|
136
|
+
private_class_method :const_unit
|
|
137
|
+
|
|
104
138
|
private
|
|
105
139
|
|
|
140
|
+
# mutant:disable
|
|
106
141
|
def make_report(label, attribute_name)
|
|
107
142
|
["#{label}:"].concat(public_send(attribute_name).either(method(:report_exception), ->(value) { [value] }))
|
|
108
143
|
end
|
|
109
144
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
145
|
+
# mutant:disable
|
|
146
|
+
def report_exception(phase_exception)
|
|
147
|
+
if phase_exception
|
|
148
|
+
[phase_exception.inspect].concat(phase_exception.backtrace&.take(20) || [])
|
|
113
149
|
else
|
|
114
|
-
[
|
|
150
|
+
%w[undefined]
|
|
115
151
|
end
|
|
116
152
|
end
|
|
117
153
|
|
|
154
|
+
# mutant:disable
|
|
118
155
|
def node_diff_report
|
|
119
156
|
diff = nil
|
|
120
157
|
|
|
@@ -130,14 +167,13 @@ module Unparser
|
|
|
130
167
|
diff ? ['Node-Diff:', diff] : []
|
|
131
168
|
end
|
|
132
169
|
|
|
133
|
-
def self.const_unit(_value); end
|
|
134
|
-
private_class_method :const_unit
|
|
135
|
-
|
|
136
170
|
class Literal < self
|
|
171
|
+
# mutant:disable
|
|
137
172
|
def success?
|
|
138
173
|
original_source.eql?(generated_source)
|
|
139
174
|
end
|
|
140
175
|
|
|
176
|
+
# mutant:disable
|
|
141
177
|
def report
|
|
142
178
|
message = [identification]
|
|
143
179
|
|
|
@@ -153,20 +189,26 @@ module Unparser
|
|
|
153
189
|
|
|
154
190
|
private
|
|
155
191
|
|
|
192
|
+
# mutant:disable
|
|
156
193
|
def source_diff_report
|
|
157
194
|
diff = nil
|
|
158
195
|
|
|
159
196
|
original_source.fmap do |original|
|
|
160
197
|
generated_source.fmap do |generated|
|
|
161
198
|
diff = Diff.new(
|
|
162
|
-
original.split("\n", -1),
|
|
163
|
-
generated.split("\n", -1)
|
|
199
|
+
encode(original).split("\n", -1),
|
|
200
|
+
encode(generated).split("\n", -1)
|
|
164
201
|
).colorized_diff
|
|
165
202
|
end
|
|
166
203
|
end
|
|
167
204
|
|
|
168
205
|
diff ? ['Source-Diff:', diff] : []
|
|
169
206
|
end
|
|
207
|
+
|
|
208
|
+
# mutant:disable
|
|
209
|
+
def encode(string)
|
|
210
|
+
string.encode('UTF-8', invalid: :replace, undef: :replace)
|
|
211
|
+
end
|
|
170
212
|
end # Literal
|
|
171
213
|
end # Validation
|
|
172
214
|
end # Unparser
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unparser
|
|
4
|
+
module Writer
|
|
5
|
+
class Array
|
|
6
|
+
include Writer, Adamantium
|
|
7
|
+
|
|
8
|
+
MAP = {
|
|
9
|
+
dsym: '%I',
|
|
10
|
+
sym: '%i',
|
|
11
|
+
dstr: '%W',
|
|
12
|
+
str: '%w'
|
|
13
|
+
}.freeze
|
|
14
|
+
private_constant(*constants(false))
|
|
15
|
+
|
|
16
|
+
def emit_compact # rubocop:disable Metrics/AbcSize
|
|
17
|
+
children_generic_type = array_elements_generic_type
|
|
18
|
+
|
|
19
|
+
write(MAP.fetch(children_generic_type))
|
|
20
|
+
|
|
21
|
+
parentheses('[', ']') do
|
|
22
|
+
delimited(children, ' ') do |child|
|
|
23
|
+
if n_sym?(child) || n_str?(child)
|
|
24
|
+
write(Util.one(child.children).to_s)
|
|
25
|
+
else
|
|
26
|
+
write('#{')
|
|
27
|
+
emitter(Util.one(Util.one(child.children).children)).write_to_buffer
|
|
28
|
+
write('}')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def array_elements_generic_type
|
|
37
|
+
children_types = children.to_set(&:type)
|
|
38
|
+
|
|
39
|
+
if children_types == Set[:sym, :dsym]
|
|
40
|
+
:dsym
|
|
41
|
+
elsif children_types == Set[:str, :dstr]
|
|
42
|
+
:dstr
|
|
43
|
+
elsif children_types == Set[]
|
|
44
|
+
:sym
|
|
45
|
+
else
|
|
46
|
+
Util.one(children_types.to_a)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end # Array
|
|
50
|
+
end # Writer
|
|
51
|
+
end # Unparser
|
|
@@ -39,7 +39,7 @@ module Unparser
|
|
|
39
39
|
tANDOP: '&&'
|
|
40
40
|
}.freeze
|
|
41
41
|
|
|
42
|
-
NEED_KEYWORD = %i[return break next].freeze
|
|
42
|
+
NEED_KEYWORD = %i[return break next match_pattern_p].freeze
|
|
43
43
|
|
|
44
44
|
private_constant(*constants(false))
|
|
45
45
|
|
|
@@ -52,9 +52,13 @@ module Unparser
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def dispatch
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if node.type.eql?(:and) && left.type.equal?(:or)
|
|
56
|
+
emit_with(KEYWORD_TOKENS)
|
|
57
|
+
else
|
|
58
|
+
left_emitter.write_to_buffer
|
|
59
|
+
write(' ', MAP.fetch(effective_symbol), ' ')
|
|
60
|
+
visit(right)
|
|
61
|
+
end
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
private
|
|
@@ -5,116 +5,122 @@ module Unparser
|
|
|
5
5
|
class DynamicString
|
|
6
6
|
include Writer, Adamantium
|
|
7
7
|
|
|
8
|
-
PATTERNS_2 =
|
|
9
|
-
[
|
|
10
|
-
%i[str_empty begin].freeze,
|
|
11
|
-
%i[begin str_nl].freeze
|
|
12
|
-
].freeze
|
|
13
|
-
|
|
14
|
-
PATTERNS_3 =
|
|
15
|
-
[
|
|
16
|
-
%i[begin str_nl_eol str_nl_eol].freeze,
|
|
17
|
-
%i[str_nl_eol begin str_nl_eol].freeze,
|
|
18
|
-
%i[str_ws begin str_nl_eol].freeze
|
|
19
|
-
].freeze
|
|
20
|
-
|
|
21
8
|
FLAT_INTERPOLATION = %i[ivar cvar gvar nth_ref].to_set.freeze
|
|
22
9
|
|
|
10
|
+
# Amount of dstr children at which heredoc emitting is
|
|
11
|
+
# preferred, but not guaranteed.
|
|
12
|
+
HEREDOC_THRESHOLD = 8
|
|
13
|
+
HEREDOC_DELIMITER = 'HEREDOC'
|
|
14
|
+
HEREDOC_HEADER = "<<-#{HEREDOC_DELIMITER}".freeze
|
|
15
|
+
HEREDOC_FOOTER = "#{HEREDOC_DELIMITER}\n".freeze
|
|
16
|
+
|
|
23
17
|
private_constant(*constants(false))
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
# The raise below is not reachable if unparser is correctly implemented
|
|
20
|
+
# but has to exist as I have to assume unparser still has bugs.
|
|
21
|
+
#
|
|
22
|
+
# But unless I had such a bug in my test corpus: I cannot enable mutant, and if I
|
|
23
|
+
# knew about such a bug: I'd fix it so would be back at the start.
|
|
24
|
+
#
|
|
25
|
+
# TLDR: Good case for a mutant disable.
|
|
26
|
+
#
|
|
27
|
+
# mutant:disable
|
|
28
|
+
def dispatch
|
|
29
|
+
# Try predictable patterns first before exhaustive search
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
+
# Pattern 1: HEREDOC for large dstr (>= 8 children, preserve readability)
|
|
32
|
+
return write_heredoc if children.length >= HEREDOC_THRESHOLD && round_trips_heredoc?
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
# Pattern 2: Limited search (prevent exponential blowup)
|
|
35
|
+
source = limited_search_segmented_source
|
|
36
|
+
return write(source) if source
|
|
37
|
+
|
|
38
|
+
# Pattern 3: HEREDOC fallback (last resort if segmentation fails)
|
|
39
|
+
return write_heredoc if round_trips_heredoc?
|
|
40
|
+
|
|
41
|
+
fail UnsupportedNodeError, "Unparser cannot round trip this node: #{node.inspect}"
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
private
|
|
41
45
|
|
|
42
|
-
def
|
|
43
|
-
|
|
46
|
+
def write_heredoc
|
|
47
|
+
write(HEREDOC_HEADER)
|
|
48
|
+
buffer.push_heredoc(heredoc_body)
|
|
44
49
|
end
|
|
45
50
|
|
|
46
|
-
def
|
|
47
|
-
|
|
51
|
+
def round_trips_heredoc?
|
|
52
|
+
round_trips?(source: heredoc_source)
|
|
48
53
|
end
|
|
54
|
+
memoize :round_trips_heredoc?
|
|
49
55
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
def limited_search_segmented_source
|
|
57
|
+
each_segments(children) do |segments|
|
|
58
|
+
source = segmented_source(segments: segments)
|
|
59
|
+
return source if round_trips?(source: source)
|
|
60
|
+
end
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
nl
|
|
56
|
-
emit_normal_heredoc_body
|
|
62
|
+
nil
|
|
57
63
|
end
|
|
58
64
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
end
|
|
65
|
+
def each_segments(array)
|
|
66
|
+
yield [array]
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
1.upto(array.length) do |take|
|
|
69
|
+
prefix = [array.take(take)]
|
|
70
|
+
suffix = array.drop(take)
|
|
71
|
+
each_segments(suffix) do |items|
|
|
72
|
+
yield(prefix + items)
|
|
73
|
+
end
|
|
68
74
|
end
|
|
69
75
|
end
|
|
70
76
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
:str_nl
|
|
74
|
-
elsif node.children.first.end_with?("\n")
|
|
75
|
-
:str_nl_eol
|
|
76
|
-
elsif str_ws?(node)
|
|
77
|
-
:str_ws
|
|
78
|
-
elsif str_empty?(node)
|
|
79
|
-
:str_empty
|
|
80
|
-
end
|
|
81
|
-
end
|
|
77
|
+
def segmented_source(segments:)
|
|
78
|
+
buffer = Buffer.new
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
Segmented.new(
|
|
81
|
+
buffer:,
|
|
82
|
+
comments:,
|
|
83
|
+
explicit_encoding: nil,
|
|
84
|
+
local_variable_scope:,
|
|
85
|
+
node:,
|
|
86
|
+
segments:
|
|
87
|
+
).dispatch
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
node.eql?(s(:str, ''))
|
|
89
|
+
buffer.content
|
|
89
90
|
end
|
|
90
91
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
def heredoc_body
|
|
93
|
+
buffer = Buffer.new
|
|
94
|
+
|
|
95
|
+
writer = Heredoc.new(
|
|
96
|
+
buffer:,
|
|
97
|
+
comments:,
|
|
98
|
+
explicit_encoding: nil,
|
|
99
|
+
local_variable_scope:,
|
|
100
|
+
node:
|
|
101
|
+
)
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
writer.emit
|
|
104
|
+
buffer.content
|
|
97
105
|
end
|
|
106
|
+
memoize :heredoc_body
|
|
98
107
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
PATTERNS_3.include?(group.map(&method(:classify)))
|
|
102
|
-
end
|
|
108
|
+
def heredoc_source
|
|
109
|
+
"#{HEREDOC_HEADER}\n#{heredoc_body}"
|
|
103
110
|
end
|
|
111
|
+
memoize :heredoc_source
|
|
112
|
+
|
|
113
|
+
class Heredoc
|
|
114
|
+
include Writer, Adamantium
|
|
104
115
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
def emit
|
|
117
|
+
emit_heredoc_body
|
|
118
|
+
write(HEREDOC_FOOTER)
|
|
108
119
|
end
|
|
109
|
-
end
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
last = children.last
|
|
113
|
-
n_str?(last) && last.children.first[-1].eql?("\n")
|
|
114
|
-
end
|
|
121
|
+
private
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
buffer.root_indent do
|
|
123
|
+
def emit_heredoc_body
|
|
118
124
|
children.each do |child|
|
|
119
125
|
if n_str?(child)
|
|
120
126
|
write(escape_dynamic(child.children.first))
|
|
@@ -123,88 +129,74 @@ module Unparser
|
|
|
123
129
|
end
|
|
124
130
|
end
|
|
125
131
|
end
|
|
126
|
-
end
|
|
127
132
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
def escape_dynamic(string)
|
|
134
|
+
string.gsub('#', '\#')
|
|
135
|
+
end
|
|
131
136
|
|
|
132
|
-
|
|
133
|
-
if FLAT_INTERPOLATION.include?(child.type)
|
|
134
|
-
write('#')
|
|
135
|
-
visit(child)
|
|
136
|
-
elsif n_dstr?(child)
|
|
137
|
-
emit_body(child.children)
|
|
138
|
-
else
|
|
137
|
+
def emit_dynamic(child)
|
|
139
138
|
write('#{')
|
|
140
139
|
emit_dynamic_component(child.children.first)
|
|
141
140
|
write('}')
|
|
142
141
|
end
|
|
143
|
-
end
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def emit_dstr
|
|
150
|
-
if children.empty?
|
|
151
|
-
write('%()')
|
|
152
|
-
else
|
|
153
|
-
segments.each_with_index do |children, index|
|
|
154
|
-
emit_segment(children, index)
|
|
155
|
-
end
|
|
143
|
+
def emit_dynamic_component(node)
|
|
144
|
+
visit(node) if node
|
|
156
145
|
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def breakpoint?(child, current)
|
|
160
|
-
last_type = current.last&.type
|
|
146
|
+
end # Heredoc
|
|
161
147
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
last_type.equal?(:dstr),
|
|
165
|
-
n_dstr?(child) && last_type
|
|
166
|
-
].any?
|
|
167
|
-
end
|
|
148
|
+
class Segmented
|
|
149
|
+
include Writer, Adamantium
|
|
168
150
|
|
|
169
|
-
|
|
170
|
-
segments = []
|
|
151
|
+
include anima.add(:segments)
|
|
171
152
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
segments
|
|
153
|
+
def dispatch
|
|
154
|
+
if children.empty?
|
|
155
|
+
write('%()')
|
|
156
|
+
else
|
|
157
|
+
segments.each_with_index { |segment, index| emit_segment(segment, index) }
|
|
177
158
|
end
|
|
178
|
-
|
|
179
|
-
current << child
|
|
180
159
|
end
|
|
181
160
|
|
|
182
|
-
|
|
183
|
-
end
|
|
161
|
+
private
|
|
184
162
|
|
|
185
|
-
|
|
186
|
-
|
|
163
|
+
def emit_segment(children, index)
|
|
164
|
+
write(' ') unless index.zero?
|
|
187
165
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
166
|
+
write('"')
|
|
167
|
+
emit_segment_body(children)
|
|
168
|
+
write('"')
|
|
169
|
+
end
|
|
192
170
|
|
|
193
|
-
|
|
194
|
-
buffer.root_indent do
|
|
171
|
+
def emit_segment_body(children)
|
|
195
172
|
children.each_with_index do |child, index|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
173
|
+
case child.type
|
|
174
|
+
when :begin
|
|
175
|
+
write('#{')
|
|
176
|
+
visit(child.children.first) if child.children.first
|
|
177
|
+
write('}')
|
|
178
|
+
when FLAT_INTERPOLATION
|
|
179
|
+
write('#')
|
|
180
|
+
visit(child)
|
|
181
|
+
when :str
|
|
182
|
+
visit_str(children, child, index)
|
|
183
|
+
when :dstr
|
|
184
|
+
emit_segment_body(child.children)
|
|
205
185
|
end
|
|
206
186
|
end
|
|
207
187
|
end
|
|
188
|
+
|
|
189
|
+
def visit_str(children, child, index)
|
|
190
|
+
string = child.children.first
|
|
191
|
+
|
|
192
|
+
next_child = children.at(index.succ)
|
|
193
|
+
|
|
194
|
+
if next_child && next_child.type.equal?(:str)
|
|
195
|
+
write(string.gsub('"', '\\"'))
|
|
196
|
+
else
|
|
197
|
+
write(child.children.first.inspect[1..-2])
|
|
198
|
+
end
|
|
199
|
+
end
|
|
208
200
|
end
|
|
209
201
|
end # DynamicString
|
|
210
202
|
end # Writer
|