prism 0.26.0 → 0.28.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -1
  3. data/Makefile +3 -2
  4. data/config.yml +305 -20
  5. data/docs/configuration.md +1 -0
  6. data/ext/prism/api_node.c +884 -879
  7. data/ext/prism/extconf.rb +23 -4
  8. data/ext/prism/extension.c +16 -9
  9. data/ext/prism/extension.h +1 -1
  10. data/include/prism/ast.h +298 -9
  11. data/include/prism/diagnostic.h +15 -5
  12. data/include/prism/options.h +2 -2
  13. data/include/prism/parser.h +10 -0
  14. data/include/prism/static_literals.h +8 -6
  15. data/include/prism/version.h +2 -2
  16. data/lib/prism/dot_visitor.rb +22 -6
  17. data/lib/prism/dsl.rb +8 -8
  18. data/lib/prism/ffi.rb +4 -4
  19. data/lib/prism/inspect_visitor.rb +2156 -0
  20. data/lib/prism/lex_compat.rb +18 -1
  21. data/lib/prism/mutation_compiler.rb +2 -2
  22. data/lib/prism/node.rb +2345 -1964
  23. data/lib/prism/node_ext.rb +34 -5
  24. data/lib/prism/parse_result/newlines.rb +0 -2
  25. data/lib/prism/parse_result.rb +137 -13
  26. data/lib/prism/pattern.rb +12 -6
  27. data/lib/prism/polyfill/byteindex.rb +13 -0
  28. data/lib/prism/polyfill/unpack1.rb +14 -0
  29. data/lib/prism/reflection.rb +21 -31
  30. data/lib/prism/serialize.rb +27 -17
  31. data/lib/prism/translation/parser/compiler.rb +34 -15
  32. data/lib/prism/translation/parser.rb +6 -6
  33. data/lib/prism/translation/ripper.rb +72 -68
  34. data/lib/prism/translation/ruby_parser.rb +69 -31
  35. data/lib/prism.rb +3 -2
  36. data/prism.gemspec +36 -38
  37. data/rbi/prism/compiler.rbi +3 -5
  38. data/rbi/prism/inspect_visitor.rbi +12 -0
  39. data/rbi/prism/node.rbi +359 -321
  40. data/rbi/prism/parse_result.rbi +85 -34
  41. data/rbi/prism/reflection.rbi +7 -13
  42. data/rbi/prism/translation/ripper.rbi +1 -11
  43. data/rbi/prism.rbi +9 -9
  44. data/sig/prism/dsl.rbs +3 -3
  45. data/sig/prism/inspect_visitor.rbs +22 -0
  46. data/sig/prism/node.rbs +68 -48
  47. data/sig/prism/parse_result.rbs +42 -10
  48. data/sig/prism/reflection.rbs +2 -8
  49. data/sig/prism/serialize.rbs +2 -3
  50. data/sig/prism.rbs +9 -9
  51. data/src/diagnostic.c +44 -24
  52. data/src/node.c +41 -16
  53. data/src/options.c +2 -2
  54. data/src/prettyprint.c +61 -18
  55. data/src/prism.c +623 -188
  56. data/src/serialize.c +5 -2
  57. data/src/static_literals.c +120 -34
  58. data/src/token_type.c +4 -4
  59. data/src/util/pm_integer.c +9 -2
  60. metadata +7 -9
  61. data/lib/prism/node_inspector.rb +0 -68
  62. data/lib/prism/polyfill/string.rb +0 -12
  63. data/rbi/prism/desugar_compiler.rbi +0 -5
  64. data/rbi/prism/mutation_compiler.rbi +0 -5
  65. data/rbi/prism/translation/parser/compiler.rbi +0 -13
  66. data/rbi/prism/translation/ripper/ripper_compiler.rbi +0 -5
  67. data/rbi/prism/translation/ruby_parser.rbi +0 -11
@@ -483,13 +483,13 @@ module Prism
483
483
  if node.parent.nil?
484
484
  builder.const_global(
485
485
  token(node.delimiter_loc),
486
- [node.child.name, srange(node.child.location)]
486
+ [node.name, srange(node.name_loc)]
487
487
  )
488
488
  else
489
489
  builder.const_fetch(
490
490
  visit(node.parent),
491
491
  token(node.delimiter_loc),
492
- [node.child.name, srange(node.child.location)]
492
+ [node.name, srange(node.name_loc)]
493
493
  )
494
494
  end
495
495
  end
@@ -839,7 +839,7 @@ module Prism
839
839
  token(node.in_loc),
840
840
  pattern,
841
841
  guard,
842
- srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset || node.location.end_offset, [";", "then"]),
842
+ srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, [";", "then"]),
843
843
  visit(node.statements)
844
844
  )
845
845
  end
@@ -1030,6 +1030,12 @@ module Prism
1030
1030
  end
1031
1031
  end
1032
1032
 
1033
+ # -> { it }
1034
+ # ^^^^^^^^^
1035
+ def visit_it_parameters_node(node)
1036
+ builder.args(nil, [], nil, false)
1037
+ end
1038
+
1033
1039
  # foo(bar: baz)
1034
1040
  # ^^^^^^^^
1035
1041
  def visit_keyword_hash_node(node)
@@ -1052,13 +1058,14 @@ module Prism
1052
1058
  # ^^^^^
1053
1059
  def visit_lambda_node(node)
1054
1060
  parameters = node.parameters
1061
+ implicit_parameters = parameters.is_a?(NumberedParametersNode) || parameters.is_a?(ItParametersNode)
1055
1062
 
1056
1063
  builder.block(
1057
1064
  builder.call_lambda(token(node.operator_loc)),
1058
1065
  [node.opening, srange(node.opening_loc)],
1059
1066
  if parameters.nil?
1060
1067
  builder.args(nil, [], nil, false)
1061
- elsif node.parameters.is_a?(NumberedParametersNode)
1068
+ elsif implicit_parameters
1062
1069
  visit(node.parameters)
1063
1070
  else
1064
1071
  builder.args(
@@ -1068,7 +1075,7 @@ module Prism
1068
1075
  false
1069
1076
  )
1070
1077
  end,
1071
- node.body&.accept(copy_compiler(forwarding: parameters.is_a?(NumberedParametersNode) ? [] : find_forwarding(parameters&.parameters))),
1078
+ node.body&.accept(copy_compiler(forwarding: implicit_parameters ? [] : find_forwarding(parameters&.parameters))),
1072
1079
  [node.closing, srange(node.closing_loc)]
1073
1080
  )
1074
1081
  end
@@ -1076,7 +1083,14 @@ module Prism
1076
1083
  # foo
1077
1084
  # ^^^
1078
1085
  def visit_local_variable_read_node(node)
1079
- builder.ident([node.name, srange(node.location)]).updated(:lvar)
1086
+ name = node.name
1087
+
1088
+ # This is just a guess. parser doesn't have support for the implicit
1089
+ # `it` variable yet, so we'll probably have to visit this once it
1090
+ # does.
1091
+ name = :it if name == :"0it"
1092
+
1093
+ builder.ident([name, srange(node.location)]).updated(:lvar)
1080
1094
  end
1081
1095
 
1082
1096
  # foo = 1
@@ -1665,7 +1679,7 @@ module Prism
1665
1679
  end
1666
1680
 
1667
1681
  # until foo; bar end
1668
- # ^^^^^^^^^^^^^^^^^
1682
+ # ^^^^^^^^^^^^^^^^^^
1669
1683
  #
1670
1684
  # bar until foo
1671
1685
  # ^^^^^^^^^^^^^
@@ -1698,7 +1712,7 @@ module Prism
1698
1712
  if node.then_keyword_loc
1699
1713
  token(node.then_keyword_loc)
1700
1714
  else
1701
- srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset || (node.conditions.last.location.end_offset + 1), [";"])
1715
+ srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, [";"])
1702
1716
  end,
1703
1717
  visit(node.statements)
1704
1718
  )
@@ -1857,12 +1871,16 @@ module Prism
1857
1871
 
1858
1872
  # Constructs a new source range by finding the given tokens between the
1859
1873
  # given start offset and end offset. If the needle is not found, it
1860
- # returns nil.
1874
+ # returns nil. Importantly it does not search past newlines or comments.
1875
+ #
1876
+ # Note that end_offset is allowed to be nil, in which case this will
1877
+ # search until the end of the string.
1861
1878
  def srange_find(start_offset, end_offset, tokens)
1862
- tokens.find do |token|
1863
- next unless (index = source_buffer.source.byteslice(start_offset...end_offset).index(token))
1864
- offset = start_offset + index
1865
- return [token, Range.new(source_buffer, offset_cache[offset], offset_cache[offset + token.length])]
1879
+ if (match = source_buffer.source.byteslice(start_offset...end_offset).match(/(\s*)(#{tokens.join("|")})/))
1880
+ _, whitespace, token = *match
1881
+ token_offset = start_offset + whitespace.bytesize
1882
+
1883
+ [token, Range.new(source_buffer, offset_cache[token_offset], offset_cache[token_offset + token.bytesize])]
1866
1884
  end
1867
1885
  end
1868
1886
 
@@ -1875,13 +1893,14 @@ module Prism
1875
1893
  def visit_block(call, block)
1876
1894
  if block
1877
1895
  parameters = block.parameters
1896
+ implicit_parameters = parameters.is_a?(NumberedParametersNode) || parameters.is_a?(ItParametersNode)
1878
1897
 
1879
1898
  builder.block(
1880
1899
  call,
1881
1900
  token(block.opening_loc),
1882
1901
  if parameters.nil?
1883
1902
  builder.args(nil, [], nil, false)
1884
- elsif parameters.is_a?(NumberedParametersNode)
1903
+ elsif implicit_parameters
1885
1904
  visit(parameters)
1886
1905
  else
1887
1906
  builder.args(
@@ -1896,7 +1915,7 @@ module Prism
1896
1915
  false
1897
1916
  )
1898
1917
  end,
1899
- block.body&.accept(copy_compiler(forwarding: parameters.is_a?(NumberedParametersNode) ? [] : find_forwarding(parameters&.parameters))),
1918
+ block.body&.accept(copy_compiler(forwarding: implicit_parameters ? [] : find_forwarding(parameters&.parameters))),
1900
1919
  token(block.closing_loc)
1901
1920
  )
1902
1921
  else
@@ -46,7 +46,7 @@ module Prism
46
46
  source = source_buffer.source
47
47
 
48
48
  offset_cache = build_offset_cache(source)
49
- result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version)), offset_cache)
49
+ result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version), scopes: [[]]), offset_cache)
50
50
 
51
51
  build_ast(result.value, offset_cache)
52
52
  ensure
@@ -59,7 +59,7 @@ module Prism
59
59
  source = source_buffer.source
60
60
 
61
61
  offset_cache = build_offset_cache(source)
62
- result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version)), offset_cache)
62
+ result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version), scopes: [[]]), offset_cache)
63
63
 
64
64
  [
65
65
  build_ast(result.value, offset_cache),
@@ -78,7 +78,7 @@ module Prism
78
78
  offset_cache = build_offset_cache(source)
79
79
  result =
80
80
  begin
81
- unwrap(Prism.parse_lex(source, filepath: source_buffer.name, version: convert_for_prism(version)), offset_cache)
81
+ unwrap(Prism.parse_lex(source, filepath: source_buffer.name, version: convert_for_prism(version), scopes: [[]]), offset_cache)
82
82
  rescue ::Parser::SyntaxError
83
83
  raise if !recover
84
84
  end
@@ -149,17 +149,17 @@ module Prism
149
149
  Diagnostic.new(:error, :endless_setter, {}, diagnostic_location, [])
150
150
  when :embdoc_term
151
151
  Diagnostic.new(:error, :embedded_document, {}, diagnostic_location, [])
152
- when :incomplete_variable_class, :incomplete_variable_class_3_3_0
152
+ when :incomplete_variable_class, :incomplete_variable_class_3_3
153
153
  location = location.copy(length: location.length + 1)
154
154
  diagnostic_location = build_range(location, offset_cache)
155
155
 
156
156
  Diagnostic.new(:error, :cvar_name, { name: location.slice }, diagnostic_location, [])
157
- when :incomplete_variable_instance, :incomplete_variable_instance_3_3_0
157
+ when :incomplete_variable_instance, :incomplete_variable_instance_3_3
158
158
  location = location.copy(length: location.length + 1)
159
159
  diagnostic_location = build_range(location, offset_cache)
160
160
 
161
161
  Diagnostic.new(:error, :ivar_name, { name: location.slice }, diagnostic_location, [])
162
- when :invalid_variable_global, :invalid_variable_global_3_3_0
162
+ when :invalid_variable_global, :invalid_variable_global_3_3
163
163
  Diagnostic.new(:error, :gvar_name, { name: location.slice }, diagnostic_location, [])
164
164
  when :module_in_method
165
165
  Diagnostic.new(:error, :module_in_def, {}, diagnostic_location, [])
@@ -19,31 +19,31 @@ module Prism
19
19
  # The main known difference is that we may omit dispatching some events in
20
20
  # some cases. This impacts the following events:
21
21
  #
22
- # * on_assign_error
23
- # * on_comma
24
- # * on_ignored_nl
25
- # * on_ignored_sp
26
- # * on_kw
27
- # * on_label_end
28
- # * on_lbrace
29
- # * on_lbracket
30
- # * on_lparen
31
- # * on_nl
32
- # * on_op
33
- # * on_operator_ambiguous
34
- # * on_rbrace
35
- # * on_rbracket
36
- # * on_rparen
37
- # * on_semicolon
38
- # * on_sp
39
- # * on_symbeg
40
- # * on_tstring_beg
41
- # * on_tstring_end
22
+ # - on_assign_error
23
+ # - on_comma
24
+ # - on_ignored_nl
25
+ # - on_ignored_sp
26
+ # - on_kw
27
+ # - on_label_end
28
+ # - on_lbrace
29
+ # - on_lbracket
30
+ # - on_lparen
31
+ # - on_nl
32
+ # - on_op
33
+ # - on_operator_ambiguous
34
+ # - on_rbrace
35
+ # - on_rbracket
36
+ # - on_rparen
37
+ # - on_semicolon
38
+ # - on_sp
39
+ # - on_symbeg
40
+ # - on_tstring_beg
41
+ # - on_tstring_end
42
42
  #
43
43
  class Ripper < Compiler
44
44
  # Parses the given Ruby program read from +src+.
45
45
  # +src+ must be a String or an IO or a object with a #gets method.
46
- def Ripper.parse(src, filename = "(ripper)", lineno = 1)
46
+ def self.parse(src, filename = "(ripper)", lineno = 1)
47
47
  new(src, filename, lineno).parse
48
48
  end
49
49
 
@@ -54,22 +54,22 @@ module Prism
54
54
  # By default, this method does not handle syntax errors in +src+,
55
55
  # use the +raise_errors+ keyword to raise a SyntaxError for an error in +src+.
56
56
  #
57
- # require 'ripper'
58
- # require 'pp'
57
+ # require "ripper"
58
+ # require "pp"
59
59
  #
60
- # pp Ripper.lex("def m(a) nil end")
61
- # #=> [[[1, 0], :on_kw, "def", FNAME ],
62
- # [[1, 3], :on_sp, " ", FNAME ],
63
- # [[1, 4], :on_ident, "m", ENDFN ],
64
- # [[1, 5], :on_lparen, "(", BEG|LABEL],
65
- # [[1, 6], :on_ident, "a", ARG ],
66
- # [[1, 7], :on_rparen, ")", ENDFN ],
67
- # [[1, 8], :on_sp, " ", BEG ],
68
- # [[1, 9], :on_kw, "nil", END ],
69
- # [[1, 12], :on_sp, " ", END ],
70
- # [[1, 13], :on_kw, "end", END ]]
60
+ # pp Ripper.lex("def m(a) nil end")
61
+ # #=> [[[1, 0], :on_kw, "def", FNAME ],
62
+ # [[1, 3], :on_sp, " ", FNAME ],
63
+ # [[1, 4], :on_ident, "m", ENDFN ],
64
+ # [[1, 5], :on_lparen, "(", BEG|LABEL],
65
+ # [[1, 6], :on_ident, "a", ARG ],
66
+ # [[1, 7], :on_rparen, ")", ENDFN ],
67
+ # [[1, 8], :on_sp, " ", BEG ],
68
+ # [[1, 9], :on_kw, "nil", END ],
69
+ # [[1, 12], :on_sp, " ", END ],
70
+ # [[1, 13], :on_kw, "end", END ]]
71
71
  #
72
- def Ripper.lex(src, filename = "-", lineno = 1, raise_errors: false)
72
+ def self.lex(src, filename = "-", lineno = 1, raise_errors: false)
73
73
  result = Prism.lex_compat(src, filepath: filename, line: lineno)
74
74
 
75
75
  if result.failure? && raise_errors
@@ -368,17 +368,17 @@ module Prism
368
368
  # returning +nil+ in such cases. Use the +raise_errors+ keyword
369
369
  # to raise a SyntaxError for an error in +src+.
370
370
  #
371
- # require "ripper"
372
- # require "pp"
371
+ # require "ripper"
372
+ # require "pp"
373
373
  #
374
- # pp Ripper.sexp("def m(a) nil end")
375
- # #=> [:program,
376
- # [[:def,
377
- # [:@ident, "m", [1, 4]],
378
- # [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil, nil, nil, nil]],
379
- # [:bodystmt, [[:var_ref, [:@kw, "nil", [1, 9]]]], nil, nil, nil]]]]
374
+ # pp Ripper.sexp("def m(a) nil end")
375
+ # #=> [:program,
376
+ # [[:def,
377
+ # [:@ident, "m", [1, 4]],
378
+ # [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil, nil, nil, nil]],
379
+ # [:bodystmt, [[:var_ref, [:@kw, "nil", [1, 9]]]], nil, nil, nil]]]]
380
380
  #
381
- def Ripper.sexp(src, filename = "-", lineno = 1, raise_errors: false)
381
+ def self.sexp(src, filename = "-", lineno = 1, raise_errors: false)
382
382
  builder = SexpBuilderPP.new(src, filename, lineno)
383
383
  sexp = builder.parse
384
384
  if builder.error?
@@ -397,23 +397,23 @@ module Prism
397
397
  # returning +nil+ in such cases. Use the +raise_errors+ keyword
398
398
  # to raise a SyntaxError for an error in +src+.
399
399
  #
400
- # require 'ripper'
401
- # require 'pp'
400
+ # require "ripper"
401
+ # require "pp"
402
402
  #
403
- # pp Ripper.sexp_raw("def m(a) nil end")
404
- # #=> [:program,
405
- # [:stmts_add,
406
- # [:stmts_new],
407
- # [:def,
408
- # [:@ident, "m", [1, 4]],
409
- # [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil]],
410
- # [:bodystmt,
411
- # [:stmts_add, [:stmts_new], [:var_ref, [:@kw, "nil", [1, 9]]]],
412
- # nil,
413
- # nil,
414
- # nil]]]]
403
+ # pp Ripper.sexp_raw("def m(a) nil end")
404
+ # #=> [:program,
405
+ # [:stmts_add,
406
+ # [:stmts_new],
407
+ # [:def,
408
+ # [:@ident, "m", [1, 4]],
409
+ # [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil]],
410
+ # [:bodystmt,
411
+ # [:stmts_add, [:stmts_new], [:var_ref, [:@kw, "nil", [1, 9]]]],
412
+ # nil,
413
+ # nil,
414
+ # nil]]]]
415
415
  #
416
- def Ripper.sexp_raw(src, filename = "-", lineno = 1, raise_errors: false)
416
+ def self.sexp_raw(src, filename = "-", lineno = 1, raise_errors: false)
417
417
  builder = SexpBuilder.new(src, filename, lineno)
418
418
  sexp = builder.parse
419
419
  if builder.error?
@@ -1456,16 +1456,16 @@ module Prism
1456
1456
  # ^^^^^^^^
1457
1457
  def visit_constant_path_node(node)
1458
1458
  if node.parent.nil?
1459
- bounds(node.child.location)
1460
- child = on_const(node.child.name.to_s)
1459
+ bounds(node.name_loc)
1460
+ child = on_const(node.name.to_s)
1461
1461
 
1462
1462
  bounds(node.location)
1463
1463
  on_top_const_ref(child)
1464
1464
  else
1465
1465
  parent = visit(node.parent)
1466
1466
 
1467
- bounds(node.child.location)
1468
- child = on_const(node.child.name.to_s)
1467
+ bounds(node.name_loc)
1468
+ child = on_const(node.name.to_s)
1469
1469
 
1470
1470
  bounds(node.location)
1471
1471
  on_const_path_ref(parent, child)
@@ -1488,16 +1488,16 @@ module Prism
1488
1488
  # Visit a constant path that is part of a write node.
1489
1489
  private def visit_constant_path_write_node_target(node)
1490
1490
  if node.parent.nil?
1491
- bounds(node.child.location)
1492
- child = on_const(node.child.name.to_s)
1491
+ bounds(node.name_loc)
1492
+ child = on_const(node.name.to_s)
1493
1493
 
1494
1494
  bounds(node.location)
1495
1495
  on_top_const_field(child)
1496
1496
  else
1497
1497
  parent = visit(node.parent)
1498
1498
 
1499
- bounds(node.child.location)
1500
- child = on_const(node.child.name.to_s)
1499
+ bounds(node.name_loc)
1500
+ child = on_const(node.name.to_s)
1501
1501
 
1502
1502
  bounds(node.location)
1503
1503
  on_const_path_field(parent, child)
@@ -3267,7 +3267,11 @@ module Prism
3267
3267
 
3268
3268
  # Lazily initialize the parse result.
3269
3269
  def result
3270
- @result ||= Prism.parse(source)
3270
+ @result ||=
3271
+ begin
3272
+ scopes = RUBY_VERSION >= "3.3.0" ? [] : [[]]
3273
+ Prism.parse(source, scopes: scopes)
3274
+ end
3271
3275
  end
3272
3276
 
3273
3277
  ##########################################################################
@@ -442,9 +442,9 @@ module Prism
442
442
  # ^^^^^^^^
443
443
  def visit_constant_path_node(node)
444
444
  if node.parent.nil?
445
- s(node, :colon3, node.child.name)
445
+ s(node, :colon3, node.name)
446
446
  else
447
- s(node, :colon2, visit(node.parent), node.child.name)
447
+ s(node, :colon2, visit(node.parent), node.name)
448
448
  end
449
449
  end
450
450
 
@@ -805,17 +805,29 @@ module Prism
805
805
  # if /foo #{bar}/ then end
806
806
  # ^^^^^^^^^^^^
807
807
  def visit_interpolated_match_last_line_node(node)
808
- s(node, :match, s(node, :dregx).concat(visit_interpolated_parts(node.parts)))
808
+ parts = visit_interpolated_parts(node.parts)
809
+ regexp =
810
+ if parts.length == 1
811
+ s(node, :lit, Regexp.new(parts.first, node.options))
812
+ else
813
+ s(node, :dregx).concat(parts).tap do |result|
814
+ options = node.options
815
+ result << options if options != 0
816
+ end
817
+ end
818
+
819
+ s(node, :match, regexp)
809
820
  end
810
821
 
811
822
  # /foo #{bar}/
812
823
  # ^^^^^^^^^^^^
813
824
  def visit_interpolated_regular_expression_node(node)
814
- if node.parts.all? { |part| part.is_a?(StringNode) || (part.is_a?(EmbeddedStatementsNode) && part.statements&.body&.length == 1 && part.statements.body.first.is_a?(StringNode)) }
815
- unescaped = node.parts.map { |part| part.is_a?(StringNode) ? part.unescaped : part.statements.body.first.unescaped }.join
816
- s(node, :lit, Regexp.new(unescaped, node.options))
825
+ parts = visit_interpolated_parts(node.parts)
826
+
827
+ if parts.length == 1
828
+ s(node, :lit, Regexp.new(parts.first, node.options))
817
829
  else
818
- s(node, :dregx).concat(visit_interpolated_parts(node.parts)).tap do |result|
830
+ s(node, :dregx).concat(parts).tap do |result|
819
831
  options = node.options
820
832
  result << options if options != 0
821
833
  end
@@ -825,45 +837,71 @@ module Prism
825
837
  # "foo #{bar}"
826
838
  # ^^^^^^^^^^^^
827
839
  def visit_interpolated_string_node(node)
828
- if (node.parts.all? { |part| part.is_a?(StringNode) || (part.is_a?(EmbeddedStatementsNode) && part.statements&.body&.length == 1 && part.statements.body.first.is_a?(StringNode)) }) ||
829
- (node.opening.nil? && node.parts.all? { |part| part.is_a?(StringNode) && !part.opening_loc.nil? })
830
- unescaped = node.parts.map { |part| part.is_a?(StringNode) ? part.unescaped : part.statements.body.first.unescaped }.join
831
- s(node, :str, unescaped)
832
- else
833
- s(node, :dstr).concat(visit_interpolated_parts(node.parts))
834
- end
840
+ parts = visit_interpolated_parts(node.parts)
841
+ parts.length == 1 ? s(node, :str, parts.first) : s(node, :dstr).concat(parts)
835
842
  end
836
843
 
837
844
  # :"foo #{bar}"
838
845
  # ^^^^^^^^^^^^^
839
846
  def visit_interpolated_symbol_node(node)
840
- if node.parts.all? { |part| part.is_a?(StringNode) || (part.is_a?(EmbeddedStatementsNode) && part.statements&.body&.length == 1 && part.statements.body.first.is_a?(StringNode)) }
841
- unescaped = node.parts.map { |part| part.is_a?(StringNode) ? part.unescaped : part.statements.body.first.unescaped }.join
842
- s(node, :lit, unescaped.to_sym)
843
- else
844
- s(node, :dsym).concat(visit_interpolated_parts(node.parts))
845
- end
847
+ parts = visit_interpolated_parts(node.parts)
848
+ parts.length == 1 ? s(node, :lit, parts.first.to_sym) : s(node, :dsym).concat(parts)
846
849
  end
847
850
 
848
851
  # `foo #{bar}`
849
852
  # ^^^^^^^^^^^^
850
853
  def visit_interpolated_x_string_node(node)
851
- children = visit_interpolated_parts(node.parts)
852
- s(node.heredoc? ? node.parts.first : node, :dxstr).concat(children)
854
+ source = node.heredoc? ? node.parts.first : node
855
+ parts = visit_interpolated_parts(node.parts)
856
+ parts.length == 1 ? s(source, :xstr, parts.first) : s(source, :dxstr).concat(parts)
853
857
  end
854
858
 
855
859
  # Visit the interpolated content of the string-like node.
856
860
  private def visit_interpolated_parts(parts)
857
- parts.each_with_object([]).with_index do |(part, results), index|
858
- if index == 0
859
- if part.is_a?(StringNode)
860
- results << part.unescaped
861
+ visited = []
862
+ parts.each do |part|
863
+ result = visit(part)
864
+
865
+ if result[0] == :evstr && result[1]
866
+ if result[1][0] == :str
867
+ visited << result[1]
868
+ elsif result[1][0] == :dstr
869
+ visited.concat(result[1][1..-1])
870
+ else
871
+ visited << result
872
+ end
873
+ else
874
+ visited << result
875
+ end
876
+ end
877
+
878
+ state = :beginning #: :beginning | :string_content | :interpolated_content
879
+
880
+ visited.each_with_object([]) do |result, results|
881
+ case state
882
+ when :beginning
883
+ if result.is_a?(String)
884
+ results << result
885
+ state = :string_content
886
+ elsif result.is_a?(Array) && result[0] == :str
887
+ results << result[1]
888
+ state = :string_content
861
889
  else
862
890
  results << ""
863
- results << visit(part)
891
+ results << result
892
+ state = :interpolated_content
893
+ end
894
+ when :string_content
895
+ if result.is_a?(String)
896
+ results[0] << result
897
+ elsif result.is_a?(Array) && result[0] == :str
898
+ results[0] << result[1]
899
+ else
900
+ results << result
901
+ state = :interpolated_content
864
902
  end
865
903
  else
866
- results << visit(part)
904
+ results << result
867
905
  end
868
906
  end
869
907
  end
@@ -1297,7 +1335,7 @@ module Prism
1297
1335
  # __FILE__
1298
1336
  # ^^^^^^^^
1299
1337
  def visit_source_file_node(node)
1300
- s(node, :str, file)
1338
+ s(node, :str, node.filepath)
1301
1339
  end
1302
1340
 
1303
1341
  # __LINE__
@@ -1498,13 +1536,13 @@ module Prism
1498
1536
  # Parse the given source and translate it into the seattlerb/ruby_parser
1499
1537
  # gem's Sexp format.
1500
1538
  def parse(source, filepath = "(string)")
1501
- translate(Prism.parse(source), filepath)
1539
+ translate(Prism.parse(source, filepath: filepath, scopes: [[]]), filepath)
1502
1540
  end
1503
1541
 
1504
1542
  # Parse the given file and translate it into the seattlerb/ruby_parser
1505
1543
  # gem's Sexp format.
1506
1544
  def parse_file(filepath)
1507
- translate(Prism.parse_file(filepath), filepath)
1545
+ translate(Prism.parse_file(filepath, scopes: [[]]), filepath)
1508
1546
  end
1509
1547
 
1510
1548
  class << self
data/lib/prism.rb CHANGED
@@ -18,10 +18,10 @@ module Prism
18
18
  autoload :Dispatcher, "prism/dispatcher"
19
19
  autoload :DotVisitor, "prism/dot_visitor"
20
20
  autoload :DSL, "prism/dsl"
21
+ autoload :InspectVisitor, "prism/inspect_visitor"
21
22
  autoload :LexCompat, "prism/lex_compat"
22
23
  autoload :LexRipper, "prism/lex_compat"
23
24
  autoload :MutationCompiler, "prism/mutation_compiler"
24
- autoload :NodeInspector, "prism/node_inspector"
25
25
  autoload :Pack, "prism/pack"
26
26
  autoload :Pattern, "prism/pattern"
27
27
  autoload :Reflection, "prism/reflection"
@@ -37,7 +37,7 @@ module Prism
37
37
  private_constant :LexRipper
38
38
 
39
39
  # :call-seq:
40
- # Prism::lex_compat(source, **options) -> ParseResult
40
+ # Prism::lex_compat(source, **options) -> LexCompat::Result
41
41
  #
42
42
  # Returns a parse result whose value is an array of tokens that closely
43
43
  # resembles the return value of Ripper::lex. The main difference is that the
@@ -67,6 +67,7 @@ module Prism
67
67
  end
68
68
  end
69
69
 
70
+ require_relative "prism/polyfill/byteindex"
70
71
  require_relative "prism/node"
71
72
  require_relative "prism/node_ext"
72
73
  require_relative "prism/parse_result"