@@ -5,6 +5,76 @@ module Translation
5
5
# This visitor is responsible for converting the syntax tree produced by
6
6
# Syntax Tree into the syntax tree produced by the whitequark/parser gem.
7
7
class Parser < BasicVisitor
8
+ # Heredocs are represented _very_ differently in the parser gem from how
9
+ # they are represented in the Syntax Tree AST. This class is responsible
10
+ # for handling the translation.
11
+ class HeredocBuilder
12
+ Line = Struct . new ( :value , :segments )
13
+
14
+ attr_reader :node , :segments
15
+
16
+ def initialize ( node )
17
+ @node = node
18
+ @segments = [ ]
19
+ end
20
+
21
+ def <<( segment )
22
+ if segment . type == :str && segments . last &&
23
+ segments . last . type == :str &&
24
+ !segments . last . children . first . end_with? ( "\n " )
25
+ segments . last . children . first << segment . children . first
26
+ else
27
+ segments << segment
28
+ end
29
+ end
30
+
31
+ def trim!
32
+ return unless node . beginning . value [ 2 ] == "~"
33
+ lines = [ Line . new ( +"" , [ ] ) ]
34
+
35
+ segments . each do |segment |
36
+ lines . last . segments << segment
37
+
38
+ if segment . type == :str
39
+ lines . last . value << segment . children . first
40
+
41
+ if lines . last . value . end_with? ( "\n " )
42
+ lines << Line . new ( +"" , [ ] )
43
+ end
44
+ end
45
+ end
46
+
47
+ lines . pop if lines . last . value . empty?
48
+ return if lines . empty?
49
+
50
+ segments . clear
51
+ lines . each do |line |
52
+ remaining = node . dedent
53
+
54
+ line . segments . each do |segment |
55
+ if segment . type == :str
56
+ if remaining > 0
57
+ whitespace = segment . children . first [ /^\s {0,#{ remaining } }/ ]
58
+ segment . children . first . sub! ( /^#{ whitespace } / , "" )
59
+ remaining -= whitespace . length
60
+ end
61
+
62
+ if node . beginning . value [ 3 ] != "'" && segments . any? &&
63
+ segments . last . type == :str &&
64
+ segments . last . children . first . end_with? ( "\\ \n " )
65
+ segments . last . children . first . gsub! ( /\\ \n \z / , "" )
66
+ segments . last . children . first . concat ( segment . children . first )
67
+ elsif !segment . children . first . empty?
68
+ segments << segment
69
+ end
70
+ else
71
+ segments << segment
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
8
78
attr_reader :buffer , :stack
9
79
10
80
def initialize ( buffer )
@@ -665,6 +735,25 @@ def visit_command_call(node)
665
735
node . end_char
666
736
end
667
737
738
+ expression =
739
+ if node . arguments . is_a? ( ArgParen )
740
+ srange ( node . start_char , node . arguments . end_char )
741
+ elsif node . arguments . is_a? ( Args ) && node . arguments . parts . any?
742
+ last_part = node . arguments . parts . last
743
+ end_char =
744
+ if last_part . is_a? ( Heredoc )
745
+ last_part . beginning . end_char
746
+ else
747
+ last_part . end_char
748
+ end
749
+
750
+ srange ( node . start_char , end_char )
751
+ elsif node . block
752
+ srange_node ( node . message )
753
+ else
754
+ srange_node ( node )
755
+ end
756
+
668
757
call =
669
758
s (
670
759
if node . operator . is_a? ( Op ) && node . operator . value == "&."
@@ -690,14 +779,7 @@ def visit_command_call(node)
690
779
node . message == :call ? nil : srange_node ( node . message ) ,
691
780
begin_token ,
692
781
end_token ,
693
- if node . arguments . is_a? ( ArgParen ) ||
694
- ( node . arguments . is_a? ( Args ) && node . arguments . parts . any? )
695
- srange ( node . start_char , node . arguments . end_char )
696
- elsif node . block
697
- srange_node ( node . message )
698
- else
699
- srange_node ( node )
700
- end
782
+ expression
701
783
)
702
784
)
703
785
@@ -1049,7 +1131,8 @@ def visit_for(node)
1049
1131
smap_for (
1050
1132
srange_length ( node . start_char , 3 ) ,
1051
1133
srange_find_between ( node . index , node . collection , "in" ) ,
1052
- srange_search_between ( node . collection , node . statements , "do" ) ,
1134
+ srange_search_between ( node . collection , node . statements , "do" ) ||
1135
+ srange_search_between ( node . collection , node . statements , ";" ) ,
1053
1136
srange_length ( node . end_char , -3 ) ,
1054
1137
srange_node ( node )
1055
1138
)
@@ -1078,98 +1161,43 @@ def visit_hash(node)
1078
1161
)
1079
1162
end
1080
1163
1081
- # Heredocs are represented _very_ differently in the parser gem from how
1082
- # they are represented in the Syntax Tree AST. This class is responsible
1083
- # for handling the translation.
1084
- class HeredocSegments
1085
- HeredocLine = Struct . new ( :value , :segments )
1086
-
1087
- attr_reader :node , :segments
1088
-
1089
- def initialize ( node )
1090
- @node = node
1091
- @segments = [ ]
1092
- end
1093
-
1094
- def <<( segment )
1095
- if segment . type == :str && segments . last &&
1096
- segments . last . type == :str &&
1097
- !segments . last . children . first . end_with? ( "\n " )
1098
- segments . last . children . first << segment . children . first
1099
- else
1100
- segments << segment
1101
- end
1102
- end
1103
-
1104
- def trim!
1105
- return unless node . beginning . value [ 2 ] == "~"
1106
- lines = [ HeredocLine . new ( +"" , [ ] ) ]
1107
-
1108
- segments . each do |segment |
1109
- lines . last . segments << segment
1110
-
1111
- if segment . type == :str
1112
- lines . last . value << segment . children . first
1113
-
1114
- if lines . last . value . end_with? ( "\n " )
1115
- lines << HeredocLine . new ( +"" , [ ] )
1116
- end
1117
- end
1118
- end
1119
-
1120
- lines . pop if lines . last . value . empty?
1121
- return if lines . empty?
1122
-
1123
- segments . clear
1124
- lines . each do |line |
1125
- remaining = node . dedent
1126
-
1127
- line . segments . each do |segment |
1128
- if segment . type == :str
1129
- if remaining > 0
1130
- whitespace = segment . children . first [ /^\s {0,#{ remaining } }/ ]
1131
- segment . children . first . sub! ( /^#{ whitespace } / , "" )
1132
- remaining -= whitespace . length
1133
- end
1134
-
1135
- if node . beginning . value [ 3 ] != "'" && segments . any? &&
1136
- segments . last . type == :str &&
1137
- segments . last . children . first . end_with? ( "\\ \n " )
1138
- segments . last . children . first . gsub! ( /\\ \n \z / , "" )
1139
- segments . last . children . first . concat ( segment . children . first )
1140
- elsif !segment . children . first . empty?
1141
- segments << segment
1142
- end
1143
- else
1144
- segments << segment
1145
- end
1146
- end
1147
- end
1148
- end
1149
- end
1150
-
1151
1164
# Visit a Heredoc node.
1152
1165
def visit_heredoc ( node )
1153
- heredoc_segments = HeredocSegments . new ( node )
1166
+ heredoc = HeredocBuilder . new ( node )
1154
1167
1168
+ # For each part of the heredoc, if it's a string content node, split it
1169
+ # into multiple string content nodes, one for each line. Otherwise,
1170
+ # visit the node as normal.
1155
1171
node . parts . each do |part |
1156
1172
if part . is_a? ( TStringContent ) && part . value . count ( "\n " ) > 1
1157
- part
1158
- . value
1159
- . split ( "\n " )
1160
- . each { |line | heredoc_segments << s ( :str , [ "#{ line } \n " ] , nil ) }
1173
+ index = part . start_char
1174
+ lines = part . value . split ( "\n " )
1175
+
1176
+ lines . each do |line |
1177
+ length = line . length + 1
1178
+ location = smap_collection_bare ( srange_length ( index , length ) )
1179
+
1180
+ heredoc << s ( :str , [ "#{ line } \n " ] , location )
1181
+ index += length
1182
+ end
1161
1183
else
1162
- heredoc_segments << visit ( part )
1184
+ heredoc << visit ( part )
1163
1185
end
1164
1186
end
1165
1187
1166
- heredoc_segments . trim!
1188
+ # Now that we have all of the pieces on the heredoc, we can trim it if
1189
+ # it is a heredoc that supports trimming (i.e., it has a ~ on the
1190
+ # declaration).
1191
+ heredoc . trim!
1192
+
1193
+ # Generate the location for the heredoc, which goes from the declaration
1194
+ # to the ending delimiter.
1167
1195
location =
1168
1196
smap_heredoc (
1169
1197
srange_node ( node . beginning ) ,
1170
1198
srange (
1171
1199
if node . parts . empty?
1172
- node . beginning . end_char
1200
+ node . beginning . end_char + 1
1173
1201
else
1174
1202
node . parts . first . start_char
1175
1203
end ,
@@ -1178,15 +1206,15 @@ def visit_heredoc(node)
1178
1206
srange ( node . ending . start_char , node . ending . end_char - 1 )
1179
1207
)
1180
1208
1209
+ # Finally, decide which kind of heredoc node to generate based on its
1210
+ # declaration and contents.
1181
1211
if node . beginning . value . match? ( /`\w +`\z / )
1182
- s ( :xstr , heredoc_segments . segments , location )
1183
- elsif heredoc_segments . segments . length > 1
1184
- s ( :dstr , heredoc_segments . segments , location )
1185
- elsif heredoc_segments . segments . empty?
1186
- s ( :dstr , [ ] , location )
1187
- else
1188
- segment = heredoc_segments . segments . first
1212
+ s ( :xstr , heredoc . segments , location )
1213
+ elsif heredoc . segments . length == 1
1214
+ segment = heredoc . segments . first
1189
1215
s ( segment . type , segment . children , location )
1216
+ else
1217
+ s ( :dstr , heredoc . segments , location )
1190
1218
end
1191
1219
end
1192
1220
0 commit comments