prism 0.29.0 → 1.3.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.
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.