@@ -5,6 +5,73 @@ module Translation
55 # This visitor is responsible for converting the syntax tree produced by
66 # Syntax Tree into the syntax tree produced by the whitequark/parser gem.
77 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+ lines << Line . new ( +"" , [ ] ) if lines . last . value . end_with? ( "\n " )
41+ end
42+ end
43+
44+ lines . pop if lines . last . value . empty?
45+ return if lines . empty?
46+
47+ segments . clear
48+ lines . each do |line |
49+ remaining = node . dedent
50+
51+ line . segments . each do |segment |
52+ if segment . type == :str
53+ if remaining > 0
54+ whitespace = segment . children . first [ /^\s {0,#{ remaining } }/ ]
55+ segment . children . first . sub! ( /^#{ whitespace } / , "" )
56+ remaining -= whitespace . length
57+ end
58+
59+ if node . beginning . value [ 3 ] != "'" && segments . any? &&
60+ segments . last . type == :str &&
61+ segments . last . children . first . end_with? ( "\\ \n " )
62+ segments . last . children . first . gsub! ( /\\ \n \z / , "" )
63+ segments . last . children . first . concat ( segment . children . first )
64+ elsif !segment . children . first . empty?
65+ segments << segment
66+ end
67+ else
68+ segments << segment
69+ end
70+ end
71+ end
72+ end
73+ end
74+
875 attr_reader :buffer , :stack
976
1077 def initialize ( buffer )
@@ -665,6 +732,25 @@ def visit_command_call(node)
665732 node . end_char
666733 end
667734
735+ expression =
736+ if node . arguments . is_a? ( ArgParen )
737+ srange ( node . start_char , node . arguments . end_char )
738+ elsif node . arguments . is_a? ( Args ) && node . arguments . parts . any?
739+ last_part = node . arguments . parts . last
740+ end_char =
741+ if last_part . is_a? ( Heredoc )
742+ last_part . beginning . end_char
743+ else
744+ last_part . end_char
745+ end
746+
747+ srange ( node . start_char , end_char )
748+ elsif node . block
749+ srange_node ( node . message )
750+ else
751+ srange_node ( node )
752+ end
753+
668754 call =
669755 s (
670756 if node . operator . is_a? ( Op ) && node . operator . value == "&."
@@ -690,14 +776,7 @@ def visit_command_call(node)
690776 node . message == :call ? nil : srange_node ( node . message ) ,
691777 begin_token ,
692778 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
779+ expression
701780 )
702781 )
703782
@@ -1049,7 +1128,8 @@ def visit_for(node)
10491128 smap_for (
10501129 srange_length ( node . start_char , 3 ) ,
10511130 srange_find_between ( node . index , node . collection , "in" ) ,
1052- srange_search_between ( node . collection , node . statements , "do" ) ,
1131+ srange_search_between ( node . collection , node . statements , "do" ) ||
1132+ srange_search_between ( node . collection , node . statements , ";" ) ,
10531133 srange_length ( node . end_char , -3 ) ,
10541134 srange_node ( node )
10551135 )
@@ -1078,98 +1158,43 @@ def visit_hash(node)
10781158 )
10791159 end
10801160
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-
11511161 # Visit a Heredoc node.
11521162 def visit_heredoc ( node )
1153- heredoc_segments = HeredocSegments . new ( node )
1163+ heredoc = HeredocBuilder . new ( node )
11541164
1165+ # For each part of the heredoc, if it's a string content node, split it
1166+ # into multiple string content nodes, one for each line. Otherwise,
1167+ # visit the node as normal.
11551168 node . parts . each do |part |
11561169 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 ) }
1170+ index = part . start_char
1171+ lines = part . value . split ( "\n " )
1172+
1173+ lines . each do |line |
1174+ length = line . length + 1
1175+ location = smap_collection_bare ( srange_length ( index , length ) )
1176+
1177+ heredoc << s ( :str , [ "#{ line } \n " ] , location )
1178+ index += length
1179+ end
11611180 else
1162- heredoc_segments << visit ( part )
1181+ heredoc << visit ( part )
11631182 end
11641183 end
11651184
1166- heredoc_segments . trim!
1185+ # Now that we have all of the pieces on the heredoc, we can trim it if
1186+ # it is a heredoc that supports trimming (i.e., it has a ~ on the
1187+ # declaration).
1188+ heredoc . trim!
1189+
1190+ # Generate the location for the heredoc, which goes from the declaration
1191+ # to the ending delimiter.
11671192 location =
11681193 smap_heredoc (
11691194 srange_node ( node . beginning ) ,
11701195 srange (
11711196 if node . parts . empty?
1172- node . beginning . end_char
1197+ node . beginning . end_char + 1
11731198 else
11741199 node . parts . first . start_char
11751200 end ,
@@ -1178,15 +1203,15 @@ def visit_heredoc(node)
11781203 srange ( node . ending . start_char , node . ending . end_char - 1 )
11791204 )
11801205
1206+ # Finally, decide which kind of heredoc node to generate based on its
1207+ # declaration and contents.
11811208 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
1209+ s ( :xstr , heredoc . segments , location )
1210+ elsif heredoc . segments . length == 1
1211+ segment = heredoc . segments . first
11891212 s ( segment . type , segment . children , location )
1213+ else
1214+ s ( :dstr , heredoc . segments , location )
11901215 end
11911216 end
11921217
0 commit comments