prism 0.29.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -1
  3. data/CONTRIBUTING.md +0 -4
  4. data/Makefile +1 -1
  5. data/README.md +4 -0
  6. data/config.yml +920 -148
  7. data/docs/build_system.md +8 -11
  8. data/docs/fuzzing.md +1 -1
  9. data/docs/parsing_rules.md +4 -1
  10. data/docs/relocation.md +34 -0
  11. data/docs/ripper_translation.md +22 -0
  12. data/docs/serialization.md +3 -0
  13. data/ext/prism/api_node.c +2863 -2079
  14. data/ext/prism/extconf.rb +14 -37
  15. data/ext/prism/extension.c +241 -391
  16. data/ext/prism/extension.h +2 -2
  17. data/include/prism/ast.h +2156 -453
  18. data/include/prism/defines.h +58 -7
  19. data/include/prism/diagnostic.h +24 -6
  20. data/include/prism/node.h +0 -21
  21. data/include/prism/options.h +94 -3
  22. data/include/prism/parser.h +82 -40
  23. data/include/prism/regexp.h +18 -8
  24. data/include/prism/static_literals.h +3 -2
  25. data/include/prism/util/pm_char.h +1 -2
  26. data/include/prism/util/pm_constant_pool.h +0 -8
  27. data/include/prism/util/pm_integer.h +22 -15
  28. data/include/prism/util/pm_newline_list.h +11 -0
  29. data/include/prism/util/pm_string.h +28 -12
  30. data/include/prism/version.h +3 -3
  31. data/include/prism.h +47 -11
  32. data/lib/prism/compiler.rb +3 -0
  33. data/lib/prism/desugar_compiler.rb +111 -74
  34. data/lib/prism/dispatcher.rb +16 -1
  35. data/lib/prism/dot_visitor.rb +55 -34
  36. data/lib/prism/dsl.rb +660 -468
  37. data/lib/prism/ffi.rb +113 -8
  38. data/lib/prism/inspect_visitor.rb +296 -64
  39. data/lib/prism/lex_compat.rb +1 -1
  40. data/lib/prism/mutation_compiler.rb +11 -6
  41. data/lib/prism/node.rb +4262 -5023
  42. data/lib/prism/node_ext.rb +91 -14
  43. data/lib/prism/parse_result/comments.rb +0 -7
  44. data/lib/prism/parse_result/errors.rb +65 -0
  45. data/lib/prism/parse_result/newlines.rb +101 -11
  46. data/lib/prism/parse_result.rb +183 -6
  47. data/lib/prism/reflection.rb +12 -10
  48. data/lib/prism/relocation.rb +504 -0
  49. data/lib/prism/serialize.rb +496 -609
  50. data/lib/prism/string_query.rb +30 -0
  51. data/lib/prism/translation/parser/compiler.rb +185 -155
  52. data/lib/prism/translation/parser/lexer.rb +26 -4
  53. data/lib/prism/translation/parser.rb +9 -4
  54. data/lib/prism/translation/ripper.rb +23 -25
  55. data/lib/prism/translation/ruby_parser.rb +86 -17
  56. data/lib/prism/visitor.rb +3 -0
  57. data/lib/prism.rb +6 -8
  58. data/prism.gemspec +9 -5
  59. data/rbi/prism/dsl.rbi +521 -0
  60. data/rbi/prism/node.rbi +1115 -1120
  61. data/rbi/prism/parse_result.rbi +29 -0
  62. data/rbi/prism/string_query.rbi +12 -0
  63. data/rbi/prism/visitor.rbi +3 -0
  64. data/rbi/prism.rbi +36 -30
  65. data/sig/prism/dsl.rbs +190 -303
  66. data/sig/prism/mutation_compiler.rbs +1 -0
  67. data/sig/prism/node.rbs +678 -632
  68. data/sig/prism/parse_result.rbs +22 -0
  69. data/sig/prism/relocation.rbs +185 -0
  70. data/sig/prism/string_query.rbs +11 -0
  71. data/sig/prism/visitor.rbs +1 -0
  72. data/sig/prism.rbs +103 -64
  73. data/src/diagnostic.c +64 -28
  74. data/src/node.c +502 -1739
  75. data/src/options.c +76 -27
  76. data/src/prettyprint.c +188 -112
  77. data/src/prism.c +3376 -2293
  78. data/src/regexp.c +208 -71
  79. data/src/serialize.c +182 -50
  80. data/src/static_literals.c +64 -85
  81. data/src/token_type.c +4 -4
  82. data/src/util/pm_char.c +1 -1
  83. data/src/util/pm_constant_pool.c +0 -8
  84. data/src/util/pm_integer.c +53 -25
  85. data/src/util/pm_newline_list.c +29 -0
  86. data/src/util/pm_string.c +131 -80
  87. data/src/util/pm_strpbrk.c +32 -6
  88. metadata +11 -7
  89. data/include/prism/util/pm_string_list.h +0 -44
  90. data/lib/prism/debug.rb +0 -249
  91. data/lib/prism/translation/parser/rubocop.rb +0 -73
  92. data/src/util/pm_string_list.c +0 -28
@@ -90,7 +90,11 @@ module Prism
90
90
  end
91
91
 
92
92
  if node.constant
93
- builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visited, nil), token(node.closing_loc))
93
+ if visited.empty?
94
+ builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(token(node.opening_loc), visited, token(node.closing_loc)), token(node.closing_loc))
95
+ else
96
+ builder.const_pattern(visit(node.constant), token(node.opening_loc), builder.array_pattern(nil, visited, nil), token(node.closing_loc))
97
+ end
94
98
  else
95
99
  builder.array_pattern(token(node.opening_loc), visited, token(node.closing_loc))
96
100
  end
@@ -105,38 +109,46 @@ module Prism
105
109
  # { a: 1 }
106
110
  # ^^^^
107
111
  def visit_assoc_node(node)
112
+ key = node.key
113
+
108
114
  if in_pattern
109
115
  if node.value.is_a?(ImplicitNode)
110
- if node.key.is_a?(SymbolNode)
111
- builder.match_hash_var([node.key.unescaped, srange(node.key.location)])
116
+ if key.is_a?(SymbolNode)
117
+ if key.opening.nil?
118
+ builder.match_hash_var([key.unescaped, srange(key.location)])
119
+ else
120
+ builder.match_hash_var_from_str(token(key.opening_loc), [builder.string_internal([key.unescaped, srange(key.value_loc)])], token(key.closing_loc))
121
+ end
112
122
  else
113
- builder.match_hash_var_from_str(token(node.key.opening_loc), visit_all(node.key.parts), token(node.key.closing_loc))
123
+ builder.match_hash_var_from_str(token(key.opening_loc), visit_all(key.parts), token(key.closing_loc))
114
124
  end
125
+ elsif key.opening.nil?
126
+ builder.pair_keyword([key.unescaped, srange(key.location)], visit(node.value))
115
127
  else
116
- builder.pair_keyword([node.key.unescaped, srange(node.key.location)], visit(node.value))
128
+ builder.pair_quoted(token(key.opening_loc), [builder.string_internal([key.unescaped, srange(key.value_loc)])], token(key.closing_loc), visit(node.value))
117
129
  end
118
130
  elsif node.value.is_a?(ImplicitNode)
119
131
  if (value = node.value.value).is_a?(LocalVariableReadNode)
120
132
  builder.pair_keyword(
121
- [node.key.unescaped, srange(node.key)],
122
- builder.ident([value.name, srange(node.key.value_loc)]).updated(:lvar)
133
+ [key.unescaped, srange(key)],
134
+ builder.ident([value.name, srange(key.value_loc)]).updated(:lvar)
123
135
  )
124
136
  else
125
- builder.pair_label([node.key.unescaped, srange(node.key.location)])
137
+ builder.pair_label([key.unescaped, srange(key.location)])
126
138
  end
127
139
  elsif node.operator_loc
128
- builder.pair(visit(node.key), token(node.operator_loc), visit(node.value))
129
- elsif node.key.is_a?(SymbolNode) && node.key.opening_loc.nil?
130
- builder.pair_keyword([node.key.unescaped, srange(node.key.location)], visit(node.value))
140
+ builder.pair(visit(key), token(node.operator_loc), visit(node.value))
141
+ elsif key.is_a?(SymbolNode) && key.opening_loc.nil?
142
+ builder.pair_keyword([key.unescaped, srange(key.location)], visit(node.value))
131
143
  else
132
144
  parts =
133
- if node.key.is_a?(SymbolNode)
134
- [builder.string_internal([node.key.unescaped, srange(node.key.value_loc)])]
145
+ if key.is_a?(SymbolNode)
146
+ [builder.string_internal([key.unescaped, srange(key.value_loc)])]
135
147
  else
136
- visit_all(node.key.parts)
148
+ visit_all(key.parts)
137
149
  end
138
150
 
139
- builder.pair_quoted(token(node.key.opening_loc), parts, token(node.key.closing_loc), visit(node.value))
151
+ builder.pair_quoted(token(key.opening_loc), parts, token(key.closing_loc), visit(node.value))
140
152
  end
141
153
  end
142
154
 
@@ -146,7 +158,9 @@ module Prism
146
158
  # { **foo }
147
159
  # ^^^^^
148
160
  def visit_assoc_splat_node(node)
149
- if node.value.nil? && forwarding.include?(:**)
161
+ if in_pattern
162
+ builder.match_rest(token(node.operator_loc), token(node.value&.location))
163
+ elsif node.value.nil? && forwarding.include?(:**)
150
164
  builder.forwarded_kwrestarg(token(node.operator_loc))
151
165
  else
152
166
  builder.kwsplat(token(node.operator_loc), visit(node.value))
@@ -167,17 +181,17 @@ module Prism
167
181
  if (rescue_clause = node.rescue_clause)
168
182
  begin
169
183
  find_start_offset = (rescue_clause.reference&.location || rescue_clause.exceptions.last&.location || rescue_clause.keyword_loc).end_offset
170
- find_end_offset = (rescue_clause.statements&.location&.start_offset || rescue_clause.consequent&.location&.start_offset || (find_start_offset + 1))
184
+ find_end_offset = (rescue_clause.statements&.location&.start_offset || rescue_clause.subsequent&.location&.start_offset || (find_start_offset + 1))
171
185
 
172
186
  rescue_bodies << builder.rescue_body(
173
187
  token(rescue_clause.keyword_loc),
174
188
  rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil,
175
189
  token(rescue_clause.operator_loc),
176
190
  visit(rescue_clause.reference),
177
- srange_find(find_start_offset, find_end_offset, [";"]),
191
+ srange_find(find_start_offset, find_end_offset, ";"),
178
192
  visit(rescue_clause.statements)
179
193
  )
180
- end until (rescue_clause = rescue_clause.consequent).nil?
194
+ end until (rescue_clause = rescue_clause.subsequent).nil?
181
195
  end
182
196
 
183
197
  begin_body =
@@ -280,7 +294,7 @@ module Prism
280
294
  visit_all(arguments),
281
295
  token(node.closing_loc),
282
296
  ),
283
- srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, ["="]),
297
+ srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, "="),
284
298
  visit(node.arguments.arguments.last)
285
299
  ),
286
300
  block
@@ -297,7 +311,7 @@ module Prism
297
311
  if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil?
298
312
  builder.assign(
299
313
  builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)),
300
- srange_find(message_loc.end_offset, node.arguments.location.start_offset, ["="]),
314
+ srange_find(message_loc.end_offset, node.arguments.location.start_offset, "="),
301
315
  visit(node.arguments.arguments.last)
302
316
  )
303
317
  else
@@ -396,8 +410,8 @@ module Prism
396
410
  token(node.case_keyword_loc),
397
411
  visit(node.predicate),
398
412
  visit_all(node.conditions),
399
- token(node.consequent&.else_keyword_loc),
400
- visit(node.consequent),
413
+ token(node.else_clause&.else_keyword_loc),
414
+ visit(node.else_clause),
401
415
  token(node.end_keyword_loc)
402
416
  )
403
417
  end
@@ -409,8 +423,8 @@ module Prism
409
423
  token(node.case_keyword_loc),
410
424
  visit(node.predicate),
411
425
  visit_all(node.conditions),
412
- token(node.consequent&.else_keyword_loc),
413
- visit(node.consequent),
426
+ token(node.else_clause&.else_keyword_loc),
427
+ visit(node.else_clause),
414
428
  token(node.end_keyword_loc)
415
429
  )
416
430
  end
@@ -719,10 +733,10 @@ module Prism
719
733
  visit(node.index),
720
734
  token(node.in_keyword_loc),
721
735
  visit(node.collection),
722
- if node.do_keyword_loc
723
- token(node.do_keyword_loc)
736
+ if (do_keyword_loc = node.do_keyword_loc)
737
+ token(do_keyword_loc)
724
738
  else
725
- srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, [";"])
739
+ srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, ";")
726
740
  end,
727
741
  visit(node.statements),
728
742
  token(node.end_keyword_loc)
@@ -844,26 +858,26 @@ module Prism
844
858
  visit(node.predicate),
845
859
  token(node.then_keyword_loc),
846
860
  visit(node.statements),
847
- token(node.consequent.else_keyword_loc),
848
- visit(node.consequent)
861
+ token(node.subsequent.else_keyword_loc),
862
+ visit(node.subsequent)
849
863
  )
850
864
  elsif node.if_keyword_loc.start_offset == node.location.start_offset
851
865
  builder.condition(
852
866
  token(node.if_keyword_loc),
853
867
  visit(node.predicate),
854
- if node.then_keyword_loc
855
- token(node.then_keyword_loc)
868
+ if (then_keyword_loc = node.then_keyword_loc)
869
+ token(then_keyword_loc)
856
870
  else
857
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.consequent&.location || node.end_keyword_loc).start_offset, [";"])
871
+ srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, ";")
858
872
  end,
859
873
  visit(node.statements),
860
- case node.consequent
874
+ case node.subsequent
861
875
  when IfNode
862
- token(node.consequent.if_keyword_loc)
876
+ token(node.subsequent.if_keyword_loc)
863
877
  when ElseNode
864
- token(node.consequent.else_keyword_loc)
878
+ token(node.subsequent.else_keyword_loc)
865
879
  end,
866
- visit(node.consequent),
880
+ visit(node.subsequent),
867
881
  if node.if_keyword != "elsif"
868
882
  token(node.end_keyword_loc)
869
883
  end
@@ -871,7 +885,7 @@ module Prism
871
885
  else
872
886
  builder.condition_mod(
873
887
  visit(node.statements),
874
- visit(node.consequent),
888
+ visit(node.subsequent),
875
889
  token(node.if_keyword_loc),
876
890
  visit(node.predicate)
877
891
  )
@@ -881,7 +895,7 @@ module Prism
881
895
  # 1i
882
896
  # ^^
883
897
  def visit_imaginary_node(node)
884
- visit_numeric(node, builder.complex([imaginary_value(node), srange(node.location)]))
898
+ visit_numeric(node, builder.complex([Complex(0, node.numeric.value), srange(node.location)]))
885
899
  end
886
900
 
887
901
  # { foo: }
@@ -917,7 +931,11 @@ module Prism
917
931
  token(node.in_loc),
918
932
  pattern,
919
933
  guard,
920
- srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, [";", "then"]),
934
+ if (then_loc = node.then_loc)
935
+ token(then_loc)
936
+ else
937
+ srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, ";")
938
+ end,
921
939
  visit(node.statements)
922
940
  )
923
941
  end
@@ -1064,36 +1082,7 @@ module Prism
1064
1082
  # ^^^^^^^^^^^^
1065
1083
  def visit_interpolated_string_node(node)
1066
1084
  if node.heredoc?
1067
- children, closing = visit_heredoc(node)
1068
- opening = token(node.opening_loc)
1069
-
1070
- start_offset = node.opening_loc.end_offset + 1
1071
- end_offset = node.parts.first.location.start_offset
1072
-
1073
- # In the below case, the offsets should be the same:
1074
- #
1075
- # <<~HEREDOC
1076
- # a #{b}
1077
- # HEREDOC
1078
- #
1079
- # But in this case, the end_offset would be greater than the start_offset:
1080
- #
1081
- # <<~HEREDOC
1082
- # #{b}
1083
- # HEREDOC
1084
- #
1085
- # So we need to make sure the result node's heredoc range is correct, without updating the children
1086
- result = if start_offset < end_offset
1087
- # We need to add a padding string to ensure that the heredoc has correct range for its body
1088
- padding_string_node = builder.string_internal(["", srange_offsets(start_offset, end_offset)])
1089
- node_with_correct_location = builder.string_compose(opening, [padding_string_node, *children], closing)
1090
- # But the padding string should not be included in the final AST, so we need to update the result's children
1091
- node_with_correct_location.updated(:dstr, children)
1092
- else
1093
- builder.string_compose(opening, children, closing)
1094
- end
1095
-
1096
- return result
1085
+ return visit_heredoc(node) { |children, closing| builder.string_compose(token(node.opening_loc), children, closing) }
1097
1086
  end
1098
1087
 
1099
1088
  parts = if node.parts.one? { |part| part.type == :string_node }
@@ -1137,8 +1126,7 @@ module Prism
1137
1126
  # ^^^^^^^^^^^^
1138
1127
  def visit_interpolated_x_string_node(node)
1139
1128
  if node.heredoc?
1140
- children, closing = visit_heredoc(node)
1141
- builder.xstring_compose(token(node.opening_loc), children, closing)
1129
+ visit_heredoc(node) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) }
1142
1130
  else
1143
1131
  builder.xstring_compose(
1144
1132
  token(node.opening_loc),
@@ -1148,6 +1136,12 @@ module Prism
1148
1136
  end
1149
1137
  end
1150
1138
 
1139
+ # -> { it }
1140
+ # ^^
1141
+ def visit_it_local_variable_read_node(node)
1142
+ builder.ident([:it, srange(node.location)]).updated(:lvar)
1143
+ end
1144
+
1151
1145
  # -> { it }
1152
1146
  # ^^^^^^^^^
1153
1147
  def visit_it_parameters_node(node)
@@ -1201,14 +1195,7 @@ module Prism
1201
1195
  # foo
1202
1196
  # ^^^
1203
1197
  def visit_local_variable_read_node(node)
1204
- name = node.name
1205
-
1206
- # This is just a guess. parser doesn't have support for the implicit
1207
- # `it` variable yet, so we'll probably have to visit this once it
1208
- # does.
1209
- name = :it if name == :"0it"
1210
-
1211
- builder.ident([name, srange(node.location)]).updated(:lvar)
1198
+ builder.ident([node.name, srange(node.location)]).updated(:lvar)
1212
1199
  end
1213
1200
 
1214
1201
  # foo = 1
@@ -1312,13 +1299,9 @@ module Prism
1312
1299
  # foo, bar = baz
1313
1300
  # ^^^^^^^^
1314
1301
  def visit_multi_target_node(node)
1315
- elements = [*node.lefts]
1316
- elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
1317
- elements.concat(node.rights)
1318
-
1319
1302
  builder.multi_lhs(
1320
1303
  token(node.lparen_loc),
1321
- visit_all(elements),
1304
+ visit_all(multi_target_elements(node)),
1322
1305
  token(node.rparen_loc)
1323
1306
  )
1324
1307
  end
@@ -1326,9 +1309,11 @@ module Prism
1326
1309
  # foo, bar = baz
1327
1310
  # ^^^^^^^^^^^^^^
1328
1311
  def visit_multi_write_node(node)
1329
- elements = [*node.lefts]
1330
- elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
1331
- elements.concat(node.rights)
1312
+ elements = multi_target_elements(node)
1313
+
1314
+ if elements.length == 1 && elements.first.is_a?(MultiTargetNode)
1315
+ elements = multi_target_elements(elements.first)
1316
+ end
1332
1317
 
1333
1318
  builder.multi_assign(
1334
1319
  builder.multi_lhs(
@@ -1409,12 +1394,12 @@ module Prism
1409
1394
 
1410
1395
  if node.requireds.any?
1411
1396
  node.requireds.each do |required|
1412
- if required.is_a?(RequiredParameterNode)
1413
- params << visit(required)
1414
- else
1415
- compiler = copy_compiler(in_destructure: true)
1416
- params << required.accept(compiler)
1417
- end
1397
+ params <<
1398
+ if required.is_a?(RequiredParameterNode)
1399
+ visit(required)
1400
+ else
1401
+ required.accept(copy_compiler(in_destructure: true))
1402
+ end
1418
1403
  end
1419
1404
  end
1420
1405
 
@@ -1423,12 +1408,12 @@ module Prism
1423
1408
 
1424
1409
  if node.posts.any?
1425
1410
  node.posts.each do |post|
1426
- if post.is_a?(RequiredParameterNode)
1427
- params << visit(post)
1428
- else
1429
- compiler = copy_compiler(in_destructure: true)
1430
- params << post.accept(compiler)
1431
- end
1411
+ params <<
1412
+ if post.is_a?(RequiredParameterNode)
1413
+ visit(post)
1414
+ else
1415
+ post.accept(copy_compiler(in_destructure: true))
1416
+ end
1432
1417
  end
1433
1418
  end
1434
1419
 
@@ -1514,7 +1499,7 @@ module Prism
1514
1499
  # 1r
1515
1500
  # ^^
1516
1501
  def visit_rational_node(node)
1517
- visit_numeric(node, builder.rational([rational_value(node), srange(node.location)]))
1502
+ visit_numeric(node, builder.rational([node.value, srange(node.location)]))
1518
1503
  end
1519
1504
 
1520
1505
  # redo
@@ -1526,9 +1511,20 @@ module Prism
1526
1511
  # /foo/
1527
1512
  # ^^^^^
1528
1513
  def visit_regular_expression_node(node)
1514
+ content = node.content
1515
+ parts =
1516
+ if content.include?("\n")
1517
+ offset = node.content_loc.start_offset
1518
+ content.lines.map do |line|
1519
+ builder.string_internal([line, srange_offsets(offset, offset += line.bytesize)])
1520
+ end
1521
+ else
1522
+ [builder.string_internal(token(node.content_loc))]
1523
+ end
1524
+
1529
1525
  builder.regexp_compose(
1530
1526
  token(node.opening_loc),
1531
- [builder.string_internal(token(node.content_loc))],
1527
+ parts,
1532
1528
  [node.closing[0], srange_offsets(node.closing_loc.start_offset, node.closing_loc.start_offset + 1)],
1533
1529
  builder.regexp_options([node.closing[1..], srange_offsets(node.closing_loc.start_offset + 1, node.closing_loc.end_offset)])
1534
1530
  )
@@ -1674,10 +1670,11 @@ module Prism
1674
1670
  # ^^^^^
1675
1671
  def visit_string_node(node)
1676
1672
  if node.heredoc?
1677
- children, closing = visit_heredoc(node.to_interpolated)
1678
- builder.string_compose(token(node.opening_loc), children, closing)
1673
+ visit_heredoc(node.to_interpolated) { |children, closing| builder.string_compose(token(node.opening_loc), children, closing) }
1679
1674
  elsif node.opening == "?"
1680
1675
  builder.character([node.unescaped, srange(node.location)])
1676
+ elsif node.opening&.start_with?("%") && node.unescaped.empty?
1677
+ builder.string_compose(token(node.opening_loc), [], token(node.closing_loc))
1681
1678
  else
1682
1679
  content_lines = node.content.lines
1683
1680
  unescaped_lines = node.unescaped.lines
@@ -1788,19 +1785,19 @@ module Prism
1788
1785
  builder.condition(
1789
1786
  token(node.keyword_loc),
1790
1787
  visit(node.predicate),
1791
- if node.then_keyword_loc
1792
- token(node.then_keyword_loc)
1788
+ if (then_keyword_loc = node.then_keyword_loc)
1789
+ token(then_keyword_loc)
1793
1790
  else
1794
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.consequent&.location || node.end_keyword_loc).start_offset, [";"])
1791
+ srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, ";")
1795
1792
  end,
1796
- visit(node.consequent),
1797
- token(node.consequent&.else_keyword_loc),
1793
+ visit(node.else_clause),
1794
+ token(node.else_clause&.else_keyword_loc),
1798
1795
  visit(node.statements),
1799
1796
  token(node.end_keyword_loc)
1800
1797
  )
1801
1798
  else
1802
1799
  builder.condition_mod(
1803
- visit(node.consequent),
1800
+ visit(node.else_clause),
1804
1801
  visit(node.statements),
1805
1802
  token(node.keyword_loc),
1806
1803
  visit(node.predicate)
@@ -1819,7 +1816,11 @@ module Prism
1819
1816
  :until,
1820
1817
  token(node.keyword_loc),
1821
1818
  visit(node.predicate),
1822
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]),
1819
+ if (do_keyword_loc = node.do_keyword_loc)
1820
+ token(do_keyword_loc)
1821
+ else
1822
+ srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";")
1823
+ end,
1823
1824
  visit(node.statements),
1824
1825
  token(node.closing_loc)
1825
1826
  )
@@ -1839,10 +1840,10 @@ module Prism
1839
1840
  builder.when(
1840
1841
  token(node.keyword_loc),
1841
1842
  visit_all(node.conditions),
1842
- if node.then_keyword_loc
1843
- token(node.then_keyword_loc)
1843
+ if (then_keyword_loc = node.then_keyword_loc)
1844
+ token(then_keyword_loc)
1844
1845
  else
1845
- srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, [";"])
1846
+ srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, ";")
1846
1847
  end,
1847
1848
  visit(node.statements)
1848
1849
  )
@@ -1859,7 +1860,11 @@ module Prism
1859
1860
  :while,
1860
1861
  token(node.keyword_loc),
1861
1862
  visit(node.predicate),
1862
- srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]),
1863
+ if (do_keyword_loc = node.do_keyword_loc)
1864
+ token(do_keyword_loc)
1865
+ else
1866
+ srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, ";")
1867
+ end,
1863
1868
  visit(node.statements),
1864
1869
  token(node.closing_loc)
1865
1870
  )
@@ -1877,8 +1882,7 @@ module Prism
1877
1882
  # ^^^^^
1878
1883
  def visit_x_string_node(node)
1879
1884
  if node.heredoc?
1880
- children, closing = visit_heredoc(node.to_interpolated)
1881
- builder.xstring_compose(token(node.opening_loc), children, closing)
1885
+ visit_heredoc(node.to_interpolated) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) }
1882
1886
  else
1883
1887
  parts = if node.unescaped.lines.one?
1884
1888
  [builder.string_internal([node.unescaped, srange(node.content_loc)])]
@@ -1940,10 +1944,12 @@ module Prism
1940
1944
  forwarding
1941
1945
  end
1942
1946
 
1943
- # Because we have mutated the AST to allow for newlines in the middle of
1944
- # a rational, we need to manually handle the value here.
1945
- def imaginary_value(node)
1946
- Complex(0, node.numeric.is_a?(RationalNode) ? rational_value(node.numeric) : node.numeric.value)
1947
+ # Returns the set of targets for a MultiTargetNode or a MultiWriteNode.
1948
+ def multi_target_elements(node)
1949
+ elements = [*node.lefts]
1950
+ elements << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode)
1951
+ elements.concat(node.rights)
1952
+ elements
1947
1953
  end
1948
1954
 
1949
1955
  # Negate the value of a numeric node. This is a special case where you
@@ -1955,7 +1961,9 @@ module Prism
1955
1961
  case receiver.type
1956
1962
  when :integer_node, :float_node
1957
1963
  receiver.copy(value: -receiver.value, location: message_loc.join(receiver.location))
1958
- when :rational_node, :imaginary_node
1964
+ when :rational_node
1965
+ receiver.copy(numerator: -receiver.numerator, location: message_loc.join(receiver.location))
1966
+ when :imaginary_node
1959
1967
  receiver.copy(numeric: numeric_negate(message_loc, receiver.numeric), location: message_loc.join(receiver.location))
1960
1968
  end
1961
1969
  end
@@ -1974,16 +1982,6 @@ module Prism
1974
1982
  parameters.block.nil?
1975
1983
  end
1976
1984
 
1977
- # Because we have mutated the AST to allow for newlines in the middle of
1978
- # a rational, we need to manually handle the value here.
1979
- def rational_value(node)
1980
- if node.numeric.is_a?(IntegerNode)
1981
- Rational(node.numeric.value)
1982
- else
1983
- Rational(node.slice.gsub(/\s/, "").chomp("r"))
1984
- end
1985
- end
1986
-
1987
1985
  # Locations in the parser gem AST are generated using this class. We
1988
1986
  # store a reference to its constant to make it slightly faster to look
1989
1987
  # up.
@@ -1999,18 +1997,16 @@ module Prism
1999
1997
  Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset])
2000
1998
  end
2001
1999
 
2002
- # Constructs a new source range by finding the given tokens between the
2003
- # given start offset and end offset. If the needle is not found, it
2000
+ # Constructs a new source range by finding the given character between
2001
+ # the given start offset and end offset. If the needle is not found, it
2004
2002
  # returns nil. Importantly it does not search past newlines or comments.
2005
2003
  #
2006
2004
  # Note that end_offset is allowed to be nil, in which case this will
2007
2005
  # search until the end of the string.
2008
- def srange_find(start_offset, end_offset, tokens)
2009
- if (match = source_buffer.source.byteslice(start_offset...end_offset).match(/(\s*)(#{tokens.join("|")})/))
2010
- _, whitespace, token = *match
2011
- token_offset = start_offset + whitespace.bytesize
2012
-
2013
- [token, Range.new(source_buffer, offset_cache[token_offset], offset_cache[token_offset + token.bytesize])]
2006
+ def srange_find(start_offset, end_offset, character)
2007
+ if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*#{character}/])
2008
+ final_offset = start_offset + match.bytesize
2009
+ [character, Range.new(source_buffer, offset_cache[final_offset - character.bytesize], offset_cache[final_offset])]
2014
2010
  end
2015
2011
  end
2016
2012
 
@@ -2037,7 +2033,8 @@ module Prism
2037
2033
  token(parameters.opening_loc),
2038
2034
  if procarg0?(parameters.parameters)
2039
2035
  parameter = parameters.parameters.requireds.first
2040
- [builder.procarg0(visit(parameter))].concat(visit_all(parameters.locals))
2036
+ visited = parameter.is_a?(RequiredParameterNode) ? visit(parameter) : parameter.accept(copy_compiler(in_destructure: true))
2037
+ [builder.procarg0(visited)].concat(visit_all(parameters.locals))
2041
2038
  else
2042
2039
  visit(parameters)
2043
2040
  end,
@@ -2053,29 +2050,55 @@ module Prism
2053
2050
  end
2054
2051
  end
2055
2052
 
2053
+ # The parser gem automatically converts \r\n to \n, meaning our offsets
2054
+ # need to be adjusted to always subtract 1 from the length.
2055
+ def chomped_bytesize(line)
2056
+ chomped = line.chomp
2057
+ chomped.bytesize + (chomped == line ? 0 : 1)
2058
+ end
2059
+
2056
2060
  # Visit a heredoc that can be either a string or an xstring.
2057
2061
  def visit_heredoc(node)
2058
2062
  children = Array.new
2063
+ indented = false
2064
+
2065
+ # If this is a dedenting heredoc, then we need to insert the opening
2066
+ # content into the children as well.
2067
+ if node.opening.start_with?("<<~") && node.parts.length > 0 && !node.parts.first.is_a?(StringNode)
2068
+ location = node.parts.first.location
2069
+ location = location.copy(start_offset: location.start_offset - location.start_line_slice.bytesize)
2070
+ children << builder.string_internal(token(location))
2071
+ indented = true
2072
+ end
2073
+
2059
2074
  node.parts.each do |part|
2060
2075
  pushing =
2061
2076
  if part.is_a?(StringNode) && part.unescaped.include?("\n")
2062
- unescaped = part.unescaped.lines(chomp: true)
2063
- escaped = part.content.lines(chomp: true)
2077
+ unescaped = part.unescaped.lines
2078
+ escaped = part.content.lines
2064
2079
 
2065
- escaped_lengths =
2066
- if node.opening.end_with?("'")
2067
- escaped.map { |line| line.bytesize + 1 }
2068
- else
2069
- escaped.chunk_while { |before, after| before.match?(/(?<!\\)\\$/) }.map { |line| line.join.bytesize + line.length }
2080
+ escaped_lengths = []
2081
+ normalized_lengths = []
2082
+
2083
+ if node.opening.end_with?("'")
2084
+ escaped.each do |line|
2085
+ escaped_lengths << line.bytesize
2086
+ normalized_lengths << chomped_bytesize(line)
2070
2087
  end
2088
+ else
2089
+ escaped
2090
+ .chunk_while { |before, after| before.match?(/(?<!\\)\\\r?\n$/) }
2091
+ .each do |lines|
2092
+ escaped_lengths << lines.sum(&:bytesize)
2093
+ normalized_lengths << lines.sum { |line| chomped_bytesize(line) }
2094
+ end
2095
+ end
2071
2096
 
2072
2097
  start_offset = part.location.start_offset
2073
- end_offset = nil
2074
2098
 
2075
- unescaped.zip(escaped_lengths).map do |unescaped_line, escaped_length|
2076
- end_offset = start_offset + (escaped_length || 0)
2077
- inner_part = builder.string_internal(["#{unescaped_line}\n", srange_offsets(start_offset, end_offset)])
2078
- start_offset = end_offset
2099
+ unescaped.map.with_index do |unescaped_line, index|
2100
+ inner_part = builder.string_internal([unescaped_line, srange_offsets(start_offset, start_offset + normalized_lengths.fetch(index, 0))])
2101
+ start_offset += escaped_lengths.fetch(index, 0)
2079
2102
  inner_part
2080
2103
  end
2081
2104
  else
@@ -2086,7 +2109,12 @@ module Prism
2086
2109
  if child.type == :str && child.children.last == ""
2087
2110
  # nothing
2088
2111
  elsif child.type == :str && children.last && children.last.type == :str && !children.last.children.first.end_with?("\n")
2089
- children.last.children.first << child.children.first
2112
+ appendee = children[-1]
2113
+
2114
+ location = appendee.loc
2115
+ location = location.with_expression(location.expression.join(child.loc.expression))
2116
+
2117
+ children[-1] = appendee.updated(:str, [appendee.children.first << child.children.first], location: location)
2090
2118
  else
2091
2119
  children << child
2092
2120
  end
@@ -2095,8 +2123,10 @@ module Prism
2095
2123
 
2096
2124
  closing = node.closing
2097
2125
  closing_t = [closing.chomp, srange_offsets(node.closing_loc.start_offset, node.closing_loc.end_offset - (closing[/\s+$/]&.length || 0))]
2126
+ composed = yield children, closing_t
2098
2127
 
2099
- [children, closing_t]
2128
+ composed = composed.updated(nil, children[1..-1]) if indented
2129
+ composed
2100
2130
  end
2101
2131
 
2102
2132
  # Visit a numeric node and account for the optional sign.