prism 1.3.0 → 1.5.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/Makefile +2 -1
  4. data/README.md +1 -0
  5. data/config.yml +273 -37
  6. data/docs/parser_translation.md +8 -23
  7. data/docs/releasing.md +1 -1
  8. data/docs/ripper_translation.md +1 -1
  9. data/docs/ruby_api.md +1 -1
  10. data/ext/prism/api_node.c +1816 -1303
  11. data/ext/prism/extension.c +244 -110
  12. data/ext/prism/extension.h +4 -4
  13. data/include/prism/ast.h +291 -49
  14. data/include/prism/defines.h +4 -1
  15. data/include/prism/diagnostic.h +4 -0
  16. data/include/prism/options.h +89 -3
  17. data/include/prism/regexp.h +2 -2
  18. data/include/prism/util/pm_buffer.h +18 -0
  19. data/include/prism/util/pm_integer.h +4 -0
  20. data/include/prism/util/pm_list.h +6 -0
  21. data/include/prism/util/pm_string.h +12 -2
  22. data/include/prism/version.h +2 -2
  23. data/include/prism.h +41 -16
  24. data/lib/prism/compiler.rb +456 -151
  25. data/lib/prism/desugar_compiler.rb +1 -0
  26. data/lib/prism/dispatcher.rb +16 -0
  27. data/lib/prism/dot_visitor.rb +21 -1
  28. data/lib/prism/dsl.rb +13 -2
  29. data/lib/prism/ffi.rb +62 -34
  30. data/lib/prism/inspect_visitor.rb +5 -1
  31. data/lib/prism/lex_compat.rb +1 -0
  32. data/lib/prism/mutation_compiler.rb +3 -0
  33. data/lib/prism/node.rb +554 -345
  34. data/lib/prism/node_ext.rb +4 -1
  35. data/lib/prism/pack.rb +2 -0
  36. data/lib/prism/parse_result/comments.rb +1 -0
  37. data/lib/prism/parse_result/errors.rb +1 -0
  38. data/lib/prism/parse_result/newlines.rb +2 -1
  39. data/lib/prism/parse_result.rb +53 -0
  40. data/lib/prism/pattern.rb +1 -0
  41. data/lib/prism/polyfill/append_as_bytes.rb +15 -0
  42. data/lib/prism/polyfill/scan_byte.rb +14 -0
  43. data/lib/prism/polyfill/warn.rb +42 -0
  44. data/lib/prism/reflection.rb +5 -2
  45. data/lib/prism/relocation.rb +1 -0
  46. data/lib/prism/serialize.rb +1275 -783
  47. data/lib/prism/string_query.rb +1 -0
  48. data/lib/prism/translation/parser/builder.rb +62 -0
  49. data/lib/prism/translation/parser/compiler.rb +230 -152
  50. data/lib/prism/translation/parser/lexer.rb +446 -64
  51. data/lib/prism/translation/parser.rb +64 -4
  52. data/lib/prism/translation/parser33.rb +1 -0
  53. data/lib/prism/translation/parser34.rb +1 -0
  54. data/lib/prism/translation/parser35.rb +13 -0
  55. data/lib/prism/translation/parser_current.rb +24 -0
  56. data/lib/prism/translation/ripper/sexp.rb +1 -0
  57. data/lib/prism/translation/ripper.rb +30 -4
  58. data/lib/prism/translation/ruby_parser.rb +291 -7
  59. data/lib/prism/translation.rb +3 -0
  60. data/lib/prism/visitor.rb +457 -152
  61. data/lib/prism.rb +5 -3
  62. data/prism.gemspec +9 -1
  63. data/rbi/prism/dsl.rbi +9 -6
  64. data/rbi/prism/node.rbi +43 -16
  65. data/rbi/prism/parse_result.rbi +17 -0
  66. data/rbi/prism/translation/parser35.rbi +6 -0
  67. data/rbi/prism.rbi +39 -36
  68. data/sig/prism/dispatcher.rbs +3 -0
  69. data/sig/prism/dsl.rbs +7 -5
  70. data/sig/prism/node.rbs +461 -37
  71. data/sig/prism/node_ext.rbs +84 -17
  72. data/sig/prism/parse_result/comments.rbs +38 -0
  73. data/sig/prism/parse_result.rbs +14 -0
  74. data/sig/prism/reflection.rbs +1 -1
  75. data/sig/prism/serialize.rbs +4 -2
  76. data/sig/prism.rbs +22 -1
  77. data/src/diagnostic.c +9 -3
  78. data/src/node.c +23 -0
  79. data/src/options.c +33 -2
  80. data/src/prettyprint.c +32 -0
  81. data/src/prism.c +620 -242
  82. data/src/serialize.c +8 -0
  83. data/src/token_type.c +36 -34
  84. data/src/util/pm_buffer.c +40 -0
  85. data/src/util/pm_constant_pool.c +6 -2
  86. data/src/util/pm_strncasecmp.c +13 -1
  87. metadata +11 -7
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
 
3
4
  module Prism
4
5
  class DesugarAndWriteNode # :nodoc:
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
 
3
4
  =begin
5
+ --
4
6
  This file is generated by the templates/template.rb script and should not be
5
7
  modified manually. See templates/lib/prism/dispatcher.rb.erb
6
8
  if you are looking to modify the template
9
+ ++
7
10
  =end
8
11
 
9
12
  module Prism
@@ -52,6 +55,19 @@ module Prism
52
55
  #
53
56
  # def register: (Listener, *Symbol) -> void
54
57
  def register(listener, *events)
58
+ register_events(listener, events)
59
+ end
60
+
61
+ # Register all public methods of a listener that match the pattern
62
+ # `on_<node_name>_(enter|leave)`.
63
+ #
64
+ # def register_public_methods: (Listener) -> void
65
+ def register_public_methods(listener)
66
+ register_events(listener, listener.public_methods(false).grep(/\Aon_.+_(?:enter|leave)\z/))
67
+ end
68
+
69
+ # Register a listener for the given events.
70
+ private def register_events(listener, events)
55
71
  events.each { |event| (listeners[event] ||= []) << listener }
56
72
  end
57
73
 
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
 
3
4
  =begin
5
+ --
4
6
  This file is generated by the templates/template.rb script and should not be
5
7
  modified manually. See templates/lib/prism/dot_visitor.rb.erb
6
8
  if you are looking to modify the template
9
+ ++
7
10
  =end
8
11
 
9
- require "cgi"
12
+ require "cgi/escape"
13
+ require "cgi/util" unless defined?(CGI::EscapeExt)
10
14
 
11
15
  module Prism
12
16
  # This visitor provides the ability to call Node#to_dot, which converts a
@@ -3608,6 +3612,9 @@ module Prism
3608
3612
  table = Table.new("ParenthesesNode")
3609
3613
  id = node_id(node)
3610
3614
 
3615
+ # flags
3616
+ table.field("flags", parentheses_node_flags_inspect(node))
3617
+
3611
3618
  # body
3612
3619
  unless (body = node.body).nil?
3613
3620
  table.field("body", port: true)
@@ -3954,6 +3961,11 @@ module Prism
3954
3961
  digraph.edge("#{id}:reference -> #{node_id(reference)};")
3955
3962
  end
3956
3963
 
3964
+ # then_keyword_loc
3965
+ unless (then_keyword_loc = node.then_keyword_loc).nil?
3966
+ table.field("then_keyword_loc", location_inspect(then_keyword_loc))
3967
+ end
3968
+
3957
3969
  # statements
3958
3970
  unless (statements = node.statements).nil?
3959
3971
  table.field("statements", port: true)
@@ -4682,6 +4694,14 @@ module Prism
4682
4694
  flags.join(", ")
4683
4695
  end
4684
4696
 
4697
+ # Inspect a node that has parentheses_node_flags flags to display the flags as a
4698
+ # comma-separated list.
4699
+ def parentheses_node_flags_inspect(node)
4700
+ flags = [] #: Array[String]
4701
+ flags << "multiple_statements" if node.multiple_statements?
4702
+ flags.join(", ")
4703
+ end
4704
+
4685
4705
  # Inspect a node that has range_flags flags to display the flags as a
4686
4706
  # comma-separated list.
4687
4707
  def range_flags_inspect(node)
data/lib/prism/dsl.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
 
3
4
  =begin
5
+ --
4
6
  This file is generated by the templates/template.rb script and should not be
5
7
  modified manually. See templates/lib/prism/dsl.rb.erb
6
8
  if you are looking to modify the template
9
+ ++
7
10
  =end
8
11
 
9
12
  module Prism
@@ -714,8 +717,8 @@ module Prism
714
717
  end
715
718
 
716
719
  # Create a new RescueNode node.
717
- def rescue_node(source: default_source, node_id: 0, location: default_location, flags: 0, keyword_loc: location, exceptions: [], operator_loc: nil, reference: nil, statements: nil, subsequent: nil)
718
- RescueNode.new(source, node_id, location, flags, keyword_loc, exceptions, operator_loc, reference, statements, subsequent)
720
+ def rescue_node(source: default_source, node_id: 0, location: default_location, flags: 0, keyword_loc: location, exceptions: [], operator_loc: nil, reference: nil, then_keyword_loc: nil, statements: nil, subsequent: nil)
721
+ RescueNode.new(source, node_id, location, flags, keyword_loc, exceptions, operator_loc, reference, then_keyword_loc, statements, subsequent)
719
722
  end
720
723
 
721
724
  # Create a new RestParameterNode node.
@@ -912,6 +915,14 @@ module Prism
912
915
  end
913
916
  end
914
917
 
918
+ # Retrieve the value of one of the ParenthesesNodeFlags flags.
919
+ def parentheses_node_flag(name)
920
+ case name
921
+ when :multiple_statements then ParenthesesNodeFlags::MULTIPLE_STATEMENTS
922
+ else Kernel.raise ArgumentError, "invalid ParenthesesNodeFlags flag: #{name.inspect}"
923
+ end
924
+ end
925
+
915
926
  # Retrieve the value of one of the RangeFlags flags.
916
927
  def range_flag(name)
917
928
  case name
data/lib/prism/ffi.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
  # typed: ignore
3
4
 
4
5
  # This file is responsible for mirroring the API provided by the C extension by
@@ -7,6 +8,10 @@
7
8
  require "rbconfig"
8
9
  require "ffi"
9
10
 
11
+ # We want to eagerly load this file if there are Ractors so that it does not get
12
+ # autoloaded from within a non-main Ractor.
13
+ require "prism/serialize" if defined?(Ractor)
14
+
10
15
  module Prism
11
16
  module LibRubyParser # :nodoc:
12
17
  extend FFI::Library
@@ -15,7 +20,8 @@ module Prism
15
20
  # must align with the build shared library from make/rake.
16
21
  libprism_in_build = File.expand_path("../../build/libprism.#{RbConfig::CONFIG["SOEXT"]}", __dir__)
17
22
  libprism_in_libdir = "#{RbConfig::CONFIG["libdir"]}/prism/libprism.#{RbConfig::CONFIG["SOEXT"]}"
18
- if File.exist? libprism_in_build
23
+
24
+ if File.exist?(libprism_in_build)
19
25
  INCLUDE_DIR = File.expand_path("../../include", __dir__)
20
26
  ffi_lib libprism_in_build
21
27
  else
@@ -80,6 +86,7 @@ module Prism
80
86
  end
81
87
 
82
88
  callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer
89
+ callback :pm_parse_stream_feof_t, [:pointer], :int
83
90
  enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY]
84
91
  enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE]
85
92
 
@@ -95,7 +102,7 @@ module Prism
95
102
  "pm_string_query_local",
96
103
  "pm_string_query_constant",
97
104
  "pm_string_query_method_name",
98
- [:pm_parse_stream_fgets_t]
105
+ [:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t]
99
106
  )
100
107
 
101
108
  load_exported_functions_from(
@@ -158,6 +165,9 @@ module Prism
158
165
  class PrismString # :nodoc:
159
166
  SIZEOF = LibRubyParser.pm_string_sizeof
160
167
 
168
+ PLATFORM_EXPECTS_UTF8 =
169
+ RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i)
170
+
161
171
  attr_reader :pointer, :length
162
172
 
163
173
  def initialize(pointer, length, from_string)
@@ -192,8 +202,7 @@ module Prism
192
202
  # On Windows and Mac, it's expected that filepaths will be encoded in
193
203
  # UTF-8. If they are not, we need to convert them to UTF-8 before
194
204
  # passing them into pm_string_mapped_init.
195
- if RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i) &&
196
- (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8
205
+ if PLATFORM_EXPECTS_UTF8 && (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8
197
206
  filepath = filepath.encode(Encoding::UTF_8)
198
207
  end
199
208
 
@@ -222,7 +231,7 @@ module Prism
222
231
  private_constant :LibRubyParser
223
232
 
224
233
  # The version constant is set by reading the result of calling pm_version.
225
- VERSION = LibRubyParser.pm_version.read_string
234
+ VERSION = LibRubyParser.pm_version.read_string.freeze
226
235
 
227
236
  class << self
228
237
  # Mirror the Prism.dump API by using the serialization API.
@@ -273,13 +282,15 @@ module Prism
273
282
  end
274
283
  }
275
284
 
285
+ eof_callback = -> (_) { stream.eof? }
286
+
276
287
  # In the pm_serialize_parse_stream function it accepts a pointer to the
277
288
  # IO object as a void* and then passes it through to the callback as the
278
289
  # third argument, but it never touches it itself. As such, since we have
279
290
  # access to the IO object already through the closure of the lambda, we
280
291
  # can pass a null pointer here and not worry.
281
- LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options))
282
- Prism.load(source, buffer.read)
292
+ LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, eof_callback, dump_options(options))
293
+ Prism.load(source, buffer.read, options.fetch(:freeze, false))
283
294
  end
284
295
  end
285
296
 
@@ -354,50 +365,37 @@ module Prism
354
365
  def dump_common(string, options) # :nodoc:
355
366
  LibRubyParser::PrismBuffer.with do |buffer|
356
367
  LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
357
- buffer.read
368
+
369
+ dumped = buffer.read
370
+ dumped.freeze if options.fetch(:freeze, false)
371
+
372
+ dumped
358
373
  end
359
374
  end
360
375
 
361
376
  def lex_common(string, code, options) # :nodoc:
362
- serialized = LibRubyParser::PrismBuffer.with do |buffer|
377
+ LibRubyParser::PrismBuffer.with do |buffer|
363
378
  LibRubyParser.pm_serialize_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
364
- buffer.read
379
+ Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false))
365
380
  end
366
-
367
- Serialize.load_tokens(Source.for(code), serialized)
368
381
  end
369
382
 
370
383
  def parse_common(string, code, options) # :nodoc:
371
384
  serialized = dump_common(string, options)
372
- Prism.load(code, serialized)
385
+ Serialize.load_parse(code, serialized, options.fetch(:freeze, false))
373
386
  end
374
387
 
375
388
  def parse_comments_common(string, code, options) # :nodoc:
376
389
  LibRubyParser::PrismBuffer.with do |buffer|
377
390
  LibRubyParser.pm_serialize_parse_comments(buffer.pointer, string.pointer, string.length, dump_options(options))
378
-
379
- source = Source.for(code)
380
- loader = Serialize::Loader.new(source, buffer.read)
381
-
382
- loader.load_header
383
- loader.load_encoding
384
- loader.load_start_line
385
- loader.load_comments
391
+ Serialize.load_parse_comments(code, buffer.read, options.fetch(:freeze, false))
386
392
  end
387
393
  end
388
394
 
389
395
  def parse_lex_common(string, code, options) # :nodoc:
390
396
  LibRubyParser::PrismBuffer.with do |buffer|
391
397
  LibRubyParser.pm_serialize_parse_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
392
-
393
- source = Source.for(code)
394
- loader = Serialize::Loader.new(source, buffer.read)
395
-
396
- tokens = loader.load_tokens
397
- node, comments, magic_comments, data_loc, errors, warnings = loader.load_nodes
398
- tokens.each { |token,| token.value.force_encoding(loader.encoding) }
399
-
400
- ParseLexResult.new([node, tokens], comments, magic_comments, data_loc, errors, warnings, source)
398
+ Serialize.load_parse_lex(code, buffer.read, options.fetch(:freeze, false))
401
399
  end
402
400
  end
403
401
 
@@ -427,11 +425,13 @@ module Prism
427
425
  def dump_options_version(version)
428
426
  case version
429
427
  when nil, "latest"
430
- 0
428
+ 0 # Handled in pm_parser_init
431
429
  when /\A3\.3(\.\d+)?\z/
432
430
  1
433
431
  when /\A3\.4(\.\d+)?\z/
434
- 0
432
+ 2
433
+ when /\A3\.5(\.\d+)?\z/
434
+ 3
435
435
  else
436
436
  raise ArgumentError, "invalid version: #{version}"
437
437
  end
@@ -480,15 +480,43 @@ module Prism
480
480
  template << "C"
481
481
  values << (options.fetch(:partial_script, false) ? 1 : 0)
482
482
 
483
+ template << "C"
484
+ values << (options.fetch(:freeze, false) ? 1 : 0)
485
+
483
486
  template << "L"
484
487
  if (scopes = options[:scopes])
485
488
  values << scopes.length
486
489
 
487
490
  scopes.each do |scope|
491
+ locals = nil
492
+ forwarding = 0
493
+
494
+ case scope
495
+ when Array
496
+ locals = scope
497
+ when Scope
498
+ locals = scope.locals
499
+
500
+ scope.forwarding.each do |forward|
501
+ case forward
502
+ when :* then forwarding |= 0x1
503
+ when :** then forwarding |= 0x2
504
+ when :& then forwarding |= 0x4
505
+ when :"..." then forwarding |= 0x8
506
+ else raise ArgumentError, "invalid forwarding value: #{forward}"
507
+ end
508
+ end
509
+ else
510
+ raise TypeError, "wrong argument type #{scope.class.inspect} (expected Array or Prism::Scope)"
511
+ end
512
+
488
513
  template << "L"
489
- values << scope.length
514
+ values << locals.length
515
+
516
+ template << "C"
517
+ values << forwarding
490
518
 
491
- scope.each do |local|
519
+ locals.each do |local|
492
520
  name = local.name
493
521
  template << "L"
494
522
  values << name.bytesize
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
 
3
4
  =begin
5
+ --
4
6
  This file is generated by the templates/template.rb script and should not be
5
7
  modified manually. See templates/lib/prism/inspect_visitor.rb.erb
6
8
  if you are looking to modify the template
9
+ ++
7
10
  =end
8
11
 
9
12
  module Prism
@@ -1879,7 +1882,7 @@ module Prism
1879
1882
  # Inspect a ParenthesesNode node.
1880
1883
  def visit_parentheses_node(node)
1881
1884
  commands << [inspect_node("ParenthesesNode", node), indent]
1882
- flags = [("newline" if node.newline?), ("static_literal" if node.static_literal?), ].compact
1885
+ flags = [("newline" if node.newline?), ("static_literal" if node.static_literal?), ("multiple_statements" if node.multiple_statements?)].compact
1883
1886
  commands << ["├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n", indent]
1884
1887
  if (body = node.body).nil?
1885
1888
  commands << ["├── body: ∅\n", indent]
@@ -2053,6 +2056,7 @@ module Prism
2053
2056
  commands << ["├── reference:\n", indent]
2054
2057
  commands << [reference, "#{indent}│ "]
2055
2058
  end
2059
+ commands << ["├── then_keyword_loc: #{inspect_location(node.then_keyword_loc)}\n", indent]
2056
2060
  if (statements = node.statements).nil?
2057
2061
  commands << ["├── statements: ∅\n", indent]
2058
2062
  else
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
 
3
4
  require "delegate"
4
5
  require "ripper"
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
+ # :markup: markdown
2
3
 
3
4
  =begin
5
+ --
4
6
  This file is generated by the templates/template.rb script and should not be
5
7
  modified manually. See templates/lib/prism/mutation_compiler.rb.erb
6
8
  if you are looking to modify the template
9
+ ++
7
10
  =end
8
11
 
9
12
  module Prism