prism 1.9.0 → 2.0.0.rc1

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 (238) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -0
  3. data/Makefile +41 -12
  4. data/README.md +35 -97
  5. data/config.yml +174 -161
  6. data/docs/build_system.md +2 -2
  7. data/docs/configuration.md +5 -4
  8. data/docs/encoding.md +2 -2
  9. data/docs/fuzzing.md +4 -11
  10. data/docs/javascript.md +19 -4
  11. data/docs/mapping.md +1 -1
  12. data/docs/releasing.md +77 -5
  13. data/docs/relocation.md +2 -4
  14. data/docs/serialization.md +21 -24
  15. data/docs/testing.md +46 -29
  16. data/ext/prism/api_node.c +1461 -1401
  17. data/ext/prism/extconf.rb +3 -4
  18. data/ext/prism/extension.c +778 -422
  19. data/ext/prism/extension.h +3 -3
  20. data/include/prism/arena.h +37 -0
  21. data/include/prism/ast.h +2727 -610
  22. data/include/prism/buffer.h +52 -0
  23. data/include/prism/comments.h +55 -0
  24. data/include/prism/compiler/accel.h +24 -0
  25. data/include/prism/compiler/align.h +36 -0
  26. data/include/prism/compiler/assume.h +25 -0
  27. data/include/prism/compiler/exported.h +24 -0
  28. data/include/prism/compiler/fallthrough.h +22 -0
  29. data/include/prism/compiler/filesystem.h +32 -0
  30. data/include/prism/compiler/flex_array.h +19 -0
  31. data/include/prism/compiler/force_inline.h +21 -0
  32. data/include/prism/compiler/format.h +25 -0
  33. data/include/prism/compiler/inline.h +17 -0
  34. data/include/prism/compiler/nodiscard.h +22 -0
  35. data/include/prism/compiler/nonnull.h +18 -0
  36. data/include/prism/compiler/unused.h +18 -0
  37. data/include/prism/constant_pool.h +81 -0
  38. data/include/prism/diagnostic.h +38 -403
  39. data/include/prism/errors_format.h +53 -0
  40. data/include/prism/excludes.h +29 -0
  41. data/include/prism/integer.h +41 -0
  42. data/include/prism/internal/allocator.h +68 -0
  43. data/include/prism/internal/allocator_debug.h +88 -0
  44. data/include/prism/internal/arena.h +108 -0
  45. data/include/prism/internal/bit.h +42 -0
  46. data/include/prism/internal/buffer.h +91 -0
  47. data/include/prism/internal/char.h +145 -0
  48. data/include/prism/internal/comments.h +53 -0
  49. data/include/prism/internal/constant_pool.h +152 -0
  50. data/include/prism/internal/diagnostic.h +389 -0
  51. data/include/prism/{encoding.h → internal/encoding.h} +27 -68
  52. data/include/prism/internal/integer.h +68 -0
  53. data/include/prism/internal/isinf.h +16 -0
  54. data/include/prism/internal/line_offset_list.h +34 -0
  55. data/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  56. data/include/prism/internal/magic_comments.h +23 -0
  57. data/include/prism/internal/memchr.h +15 -0
  58. data/include/prism/internal/node.h +38 -0
  59. data/include/prism/internal/options.h +232 -0
  60. data/include/prism/internal/parser.h +984 -0
  61. data/include/prism/internal/regexp.h +26 -0
  62. data/include/prism/internal/serialize.h +39 -0
  63. data/include/prism/internal/source.h +72 -0
  64. data/include/prism/{static_literals.h → internal/static_literals.h} +26 -47
  65. data/include/prism/internal/stringy.h +35 -0
  66. data/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  67. data/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  68. data/include/prism/internal/tokens.h +11 -0
  69. data/include/prism/json.h +32 -0
  70. data/include/prism/line_offset_list.h +61 -0
  71. data/include/prism/magic_comments.h +35 -0
  72. data/include/prism/node.h +18 -53
  73. data/include/prism/options.h +146 -280
  74. data/include/prism/parser.h +336 -868
  75. data/include/prism/prettyprint.h +6 -9
  76. data/include/prism/serialize.h +115 -0
  77. data/include/prism/source.h +155 -0
  78. data/include/prism/stream.h +28 -0
  79. data/include/prism/string_query.h +63 -0
  80. data/include/prism/stringy.h +72 -0
  81. data/include/prism/version.h +12 -3
  82. data/include/prism.h +43 -320
  83. data/lib/prism/compiler.rb +319 -305
  84. data/lib/prism/desugar_compiler.rb +105 -34
  85. data/lib/prism/dispatcher.rb +646 -473
  86. data/lib/prism/dot_visitor.rb +592 -511
  87. data/lib/prism/dsl.rb +391 -25
  88. data/lib/prism/ffi.rb +252 -90
  89. data/lib/prism/inspect_visitor.rb +357 -325
  90. data/lib/prism/lex_compat.rb +254 -212
  91. data/lib/prism/mutation_compiler.rb +313 -305
  92. data/lib/prism/node.rb +20268 -7030
  93. data/lib/prism/node_ext.rb +180 -187
  94. data/lib/prism/node_find.rb +215 -0
  95. data/lib/prism/parse_result/errors.rb +7 -1
  96. data/lib/prism/parse_result/newlines.rb +276 -8
  97. data/lib/prism/parse_result.rb +453 -139
  98. data/lib/prism/pattern.rb +65 -20
  99. data/lib/prism/reflection.rb +23 -12
  100. data/lib/prism/relocation.rb +149 -87
  101. data/lib/prism/serialize.rb +3443 -535
  102. data/lib/prism/string_query.rb +16 -1
  103. data/lib/prism/translation/parser/builder.rb +10 -2
  104. data/lib/prism/translation/parser/compiler.rb +44 -52
  105. data/lib/prism/translation/parser/lexer.rb +41 -92
  106. data/lib/prism/translation/parser.rb +1 -1
  107. data/lib/prism/translation/parser_current.rb +1 -1
  108. data/lib/prism/translation/ripper/lexer.rb +8 -10
  109. data/lib/prism/translation/ripper/sexp.rb +2 -10
  110. data/lib/prism/translation/ripper/shim.rb +2 -0
  111. data/lib/prism/translation/ripper.rb +967 -209
  112. data/lib/prism/translation/ruby_parser.rb +73 -316
  113. data/lib/prism/translation.rb +2 -0
  114. data/lib/prism/visitor.rb +632 -5
  115. data/lib/prism.rb +79 -9
  116. data/prism.gemspec +122 -60
  117. data/rbi/generated/prism/compiler.rbi +489 -0
  118. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  119. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  120. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  121. data/rbi/generated/prism/dsl.rbi +751 -0
  122. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  123. data/rbi/generated/prism/lex_compat.rbi +162 -0
  124. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  125. data/rbi/generated/prism/node.rbi +13681 -0
  126. data/rbi/generated/prism/node_ext.rbi +259 -0
  127. data/rbi/generated/prism/node_find.rbi +79 -0
  128. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  129. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  130. data/rbi/generated/prism/parse_result.rbi +764 -0
  131. data/rbi/generated/prism/pattern.rbi +134 -0
  132. data/rbi/generated/prism/reflection.rbi +89 -0
  133. data/rbi/generated/prism/relocation.rbi +329 -0
  134. data/rbi/generated/prism/serialize.rbi +181 -0
  135. data/rbi/generated/prism/string_query.rbi +36 -0
  136. data/rbi/generated/prism/translation.rbi +7 -0
  137. data/rbi/generated/prism/visitor.rbi +650 -0
  138. data/rbi/generated/prism.rbi +86 -0
  139. data/rbi/prism/translation/parser_versions.rbi +5 -0
  140. data/rbi/rubyvm/node_find.rbi +14 -0
  141. data/sig/generated/prism/compiler.rbs +492 -0
  142. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  143. data/sig/generated/prism/dispatcher.rbs +1292 -0
  144. data/sig/generated/prism/dot_visitor.rbs +635 -0
  145. data/sig/generated/prism/dsl.rbs +928 -0
  146. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  147. data/sig/generated/prism/lex_compat.rbs +166 -0
  148. data/sig/{prism → generated/prism}/mutation_compiler.rbs +309 -4
  149. data/sig/generated/prism/node.rbs +20160 -0
  150. data/sig/generated/prism/node_ext.rbs +290 -0
  151. data/sig/generated/prism/node_find.rbs +90 -0
  152. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  153. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  154. data/sig/generated/prism/parse_result.rbs +859 -0
  155. data/sig/generated/prism/pattern.rbs +156 -0
  156. data/sig/generated/prism/reflection.rbs +101 -0
  157. data/sig/generated/prism/relocation.rbs +428 -0
  158. data/sig/generated/prism/serialize.rbs +195 -0
  159. data/sig/generated/prism/string_query.rbs +36 -0
  160. data/sig/generated/prism/translation.rbs +8 -0
  161. data/sig/generated/prism/visitor.rbs +1111 -0
  162. data/sig/generated/prism.rbs +84 -0
  163. data/sig/manifest.yaml +2 -0
  164. data/src/arena.c +117 -0
  165. data/src/{util/pm_buffer.c → buffer.c} +44 -21
  166. data/src/{util/pm_char.c → char.c} +28 -57
  167. data/src/comments.c +454 -0
  168. data/src/constant_pool.c +406 -0
  169. data/src/diagnostic.c +97 -65
  170. data/src/encoding.c +8 -3
  171. data/src/errors_format.c +519 -0
  172. data/src/{util/pm_integer.c → integer.c} +33 -22
  173. data/src/json.c +5733 -0
  174. data/src/line_offset_list.c +100 -0
  175. data/src/list.c +24 -0
  176. data/src/{util/pm_memchr.c → memchr.c} +8 -6
  177. data/src/node.c +3504 -7674
  178. data/src/options.c +162 -49
  179. data/src/parser.c +349 -0
  180. data/src/prettyprint.c +421 -354
  181. data/src/prism.c +8030 -6970
  182. data/src/regexp.c +961 -45
  183. data/src/serialize.c +459 -424
  184. data/src/source.c +518 -0
  185. data/src/static_literals.c +111 -43
  186. data/src/string_query.c +166 -0
  187. data/src/stringy.c +91 -0
  188. data/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  189. data/src/strpbrk.c +447 -0
  190. data/src/{token_type.c → tokens.c} +39 -18
  191. metadata +122 -61
  192. data/ext/prism/api_pack.c +0 -276
  193. data/include/prism/defines.h +0 -260
  194. data/include/prism/pack.h +0 -163
  195. data/include/prism/regexp.h +0 -43
  196. data/include/prism/util/pm_buffer.h +0 -236
  197. data/include/prism/util/pm_char.h +0 -204
  198. data/include/prism/util/pm_constant_pool.h +0 -218
  199. data/include/prism/util/pm_integer.h +0 -130
  200. data/include/prism/util/pm_memchr.h +0 -29
  201. data/include/prism/util/pm_newline_list.h +0 -113
  202. data/include/prism/util/pm_string.h +0 -200
  203. data/lib/prism/pack.rb +0 -230
  204. data/lib/prism/parse_result/comments.rb +0 -188
  205. data/rbi/prism/compiler.rbi +0 -12
  206. data/rbi/prism/dsl.rbi +0 -524
  207. data/rbi/prism/inspect_visitor.rbi +0 -12
  208. data/rbi/prism/node.rbi +0 -8750
  209. data/rbi/prism/node_ext.rbi +0 -107
  210. data/rbi/prism/parse_result.rbi +0 -404
  211. data/rbi/prism/reflection.rbi +0 -58
  212. data/rbi/prism/string_query.rbi +0 -12
  213. data/rbi/prism/visitor.rbi +0 -473
  214. data/rbi/prism.rbi +0 -63
  215. data/sig/prism/compiler.rbs +0 -9
  216. data/sig/prism/dispatcher.rbs +0 -19
  217. data/sig/prism/dot_visitor.rbs +0 -6
  218. data/sig/prism/dsl.rbs +0 -351
  219. data/sig/prism/inspect_visitor.rbs +0 -22
  220. data/sig/prism/lex_compat.rbs +0 -10
  221. data/sig/prism/node.rbs +0 -4042
  222. data/sig/prism/node_ext.rbs +0 -149
  223. data/sig/prism/pack.rbs +0 -43
  224. data/sig/prism/parse_result/comments.rbs +0 -38
  225. data/sig/prism/parse_result.rbs +0 -197
  226. data/sig/prism/pattern.rbs +0 -13
  227. data/sig/prism/reflection.rbs +0 -50
  228. data/sig/prism/relocation.rbs +0 -185
  229. data/sig/prism/serialize.rbs +0 -8
  230. data/sig/prism/string_query.rbs +0 -11
  231. data/sig/prism/visitor.rbs +0 -169
  232. data/sig/prism.rbs +0 -272
  233. data/src/pack.c +0 -509
  234. data/src/util/pm_constant_pool.c +0 -342
  235. data/src/util/pm_list.c +0 -49
  236. data/src/util/pm_newline_list.c +0 -125
  237. data/src/util/pm_string.c +0 -381
  238. data/src/util/pm_strpbrk.c +0 -206
data/lib/prism/ffi.rb CHANGED
@@ -12,7 +12,7 @@ require "ffi"
12
12
  # autoloaded from within a non-main Ractor.
13
13
  require "prism/serialize" if defined?(Ractor)
14
14
 
15
- module Prism
15
+ module Prism # :nodoc:
16
16
  module LibRubyParser # :nodoc:
17
17
  extend FFI::Library
18
18
 
@@ -54,13 +54,18 @@ module Prism
54
54
  def self.load_exported_functions_from(header, *functions, callbacks)
55
55
  File.foreach("#{INCLUDE_DIR}/#{header}") do |line|
56
56
  # We only want to attempt to load exported functions.
57
- next unless line.start_with?("PRISM_EXPORTED_FUNCTION ")
57
+ next unless line.include?("PRISM_EXPORTED_FUNCTION ")
58
58
 
59
59
  # We only want to load the functions that we are interested in.
60
60
  next unless functions.any? { |function| line.include?(function) }
61
61
 
62
+ # Strip leading attributes (PRISM_EXPORTED_FUNCTION, PRISM_NODISCARD, etc.)
63
+ line = line.sub(/\A(PRISM_\w+(?:\([^)]*\))?)+/, "")
64
+ # Strip trailing attributes (PRISM_NODISCARD, PRISM_NONNULL(...), etc.)
65
+ line = line.sub(/\)(\s+PRISM_\w+(?:\([^)]*\))?)+\s*;/, ");")
66
+
62
67
  # Parse the function declaration.
63
- unless /^PRISM_EXPORTED_FUNCTION (?<return_type>.+) (?<name>\w+)\((?<arg_types>.+)\);$/ =~ line
68
+ unless /^(?<return_type>.+) (?<name>\w+)\((?<arg_types>.+)\);$/ =~ line
64
69
  raise "Could not parse #{line}"
65
70
  end
66
71
 
@@ -85,30 +90,46 @@ module Prism
85
90
  raise "Could not find functions #{functions.inspect}" unless functions.empty?
86
91
  end
87
92
 
88
- callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer
89
- callback :pm_parse_stream_feof_t, [:pointer], :int
90
- enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY]
93
+ callback :pm_source_stream_fgets_t, [:pointer, :int, :pointer], :pointer
94
+ callback :pm_source_stream_feof_t, [:pointer], :int
95
+ pm_source_init_result_values = %i[PM_SOURCE_INIT_SUCCESS PM_SOURCE_INIT_ERROR_GENERIC PM_SOURCE_INIT_ERROR_DIRECTORY PM_SOURCE_INIT_ERROR_NON_REGULAR]
96
+ enum :pm_source_init_result_t, pm_source_init_result_values
91
97
  enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE]
98
+ enum :pm_errors_format_type_t, [:PM_ERRORS_FORMAT_PLAIN, 1, :PM_ERRORS_FORMAT_STYLE, :PM_ERRORS_FORMAT_COLOR]
99
+
100
+ # Ractor-safe lookup table for pm_source_init_result_t, since FFI's
101
+ # enum_type accesses module instance variables that are not shareable.
102
+ SOURCE_INIT_RESULT = pm_source_init_result_values.freeze
92
103
 
93
104
  load_exported_functions_from(
94
- "prism.h",
105
+ "prism/version.h",
95
106
  "pm_version",
107
+ []
108
+ )
109
+
110
+ load_exported_functions_from(
111
+ "prism/serialize.h",
96
112
  "pm_serialize_parse",
97
113
  "pm_serialize_parse_stream",
98
114
  "pm_serialize_parse_comments",
99
115
  "pm_serialize_lex",
100
116
  "pm_serialize_parse_lex",
101
- "pm_parse_success_p",
117
+ "pm_serialize_parse_success_p",
118
+ "pm_serialize_parse_errors_format",
119
+ []
120
+ )
121
+
122
+ load_exported_functions_from(
123
+ "prism/string_query.h",
102
124
  "pm_string_query_local",
103
125
  "pm_string_query_constant",
104
126
  "pm_string_query_method_name",
105
- [:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t]
127
+ []
106
128
  )
107
129
 
108
130
  load_exported_functions_from(
109
- "prism/util/pm_buffer.h",
110
- "pm_buffer_sizeof",
111
- "pm_buffer_init",
131
+ "prism/buffer.h",
132
+ "pm_buffer_new",
112
133
  "pm_buffer_value",
113
134
  "pm_buffer_length",
114
135
  "pm_buffer_free",
@@ -116,20 +137,19 @@ module Prism
116
137
  )
117
138
 
118
139
  load_exported_functions_from(
119
- "prism/util/pm_string.h",
120
- "pm_string_mapped_init",
121
- "pm_string_free",
122
- "pm_string_source",
123
- "pm_string_length",
124
- "pm_string_sizeof",
125
- []
140
+ "prism/source.h",
141
+ "pm_source_file_new",
142
+ "pm_source_mapped_new",
143
+ "pm_source_stream_new",
144
+ "pm_source_free",
145
+ "pm_source_source",
146
+ "pm_source_length",
147
+ [:pm_source_stream_fgets_t, :pm_source_stream_feof_t]
126
148
  )
127
149
 
128
150
  # This object represents a pm_buffer_t. We only use it as an opaque pointer,
129
151
  # so it doesn't need to know the fields of pm_buffer_t.
130
152
  class PrismBuffer # :nodoc:
131
- SIZEOF = LibRubyParser.pm_buffer_sizeof
132
-
133
153
  attr_reader :pointer
134
154
 
135
155
  def initialize(pointer)
@@ -151,22 +171,22 @@ module Prism
151
171
  # Initialize a new buffer and yield it to the block. The buffer will be
152
172
  # automatically freed when the block returns.
153
173
  def self.with
154
- FFI::MemoryPointer.new(SIZEOF) do |pointer|
155
- raise unless LibRubyParser.pm_buffer_init(pointer)
156
- return yield new(pointer)
174
+ buffer = LibRubyParser.pm_buffer_new
175
+ raise unless buffer
176
+
177
+ begin
178
+ yield new(buffer)
157
179
  ensure
158
- LibRubyParser.pm_buffer_free(pointer)
180
+ LibRubyParser.pm_buffer_free(buffer)
159
181
  end
160
182
  end
161
183
  end
162
184
 
163
- # This object represents a pm_string_t. We only use it as an opaque pointer,
164
- # so it doesn't have to be an FFI::Struct.
165
- class PrismString # :nodoc:
166
- SIZEOF = LibRubyParser.pm_string_sizeof
167
-
185
+ # This object represents source code to be parsed. For strings it wraps a
186
+ # pointer directly; for files it uses a pm_source_t under the hood.
187
+ class PrismSource # :nodoc:
168
188
  PLATFORM_EXPECTS_UTF8 =
169
- RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i)
189
+ RbConfig::CONFIG["host_os"].match?(/cygwin|mingw|mswin|darwin/i)
170
190
 
171
191
  attr_reader :pointer, :length
172
192
 
@@ -181,7 +201,7 @@ module Prism
181
201
  @pointer.read_string(@length)
182
202
  end
183
203
 
184
- # Yields a pm_string_t pointer to the given block.
204
+ # Yields a PrismSource backed by the given string to the block.
185
205
  def self.with_string(string)
186
206
  raise TypeError unless string.is_a?(String)
187
207
 
@@ -195,32 +215,43 @@ module Prism
195
215
  end
196
216
  end
197
217
 
198
- # Yields a pm_string_t pointer to the given block.
199
- def self.with_file(filepath)
200
- raise TypeError unless filepath.is_a?(String)
218
+ # Yields a PrismSource to the given block, backed by a pm_source_t.
219
+ def self.with_file(filepath, options)
220
+ # File.path applies the same coercion and validation as the C
221
+ # extension's rb_get_path: it accepts to_path/to_str objects and
222
+ # rejects paths containing a NUL byte, which would otherwise truncate
223
+ # the path when it is handed to C as a NUL-terminated string.
224
+ filepath = File.path(filepath)
225
+ options[:filepath] = filepath
201
226
 
202
227
  # On Windows and Mac, it's expected that filepaths will be encoded in
203
228
  # UTF-8. If they are not, we need to convert them to UTF-8 before
204
- # passing them into pm_string_mapped_init.
229
+ # passing them into pm_source_mapped_new.
205
230
  if PLATFORM_EXPECTS_UTF8 && (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8
206
231
  filepath = filepath.encode(Encoding::UTF_8)
207
232
  end
208
233
 
209
- FFI::MemoryPointer.new(SIZEOF) do |pm_string|
210
- case (result = LibRubyParser.pm_string_mapped_init(pm_string, filepath))
211
- when :PM_STRING_INIT_SUCCESS
212
- pointer = LibRubyParser.pm_string_source(pm_string)
213
- length = LibRubyParser.pm_string_length(pm_string)
234
+ FFI::MemoryPointer.new(:int) do |result_ptr|
235
+ pm_source = LibRubyParser.pm_source_mapped_new(filepath, 0, result_ptr)
236
+
237
+ case SOURCE_INIT_RESULT[result_ptr.read_int]
238
+ when :PM_SOURCE_INIT_SUCCESS
239
+ pointer = LibRubyParser.pm_source_source(pm_source)
240
+ length = LibRubyParser.pm_source_length(pm_source)
214
241
  return yield new(pointer, length, false)
215
- when :PM_STRING_INIT_ERROR_GENERIC
242
+ when :PM_SOURCE_INIT_ERROR_GENERIC
216
243
  raise SystemCallError.new(filepath, FFI.errno)
217
- when :PM_STRING_INIT_ERROR_DIRECTORY
244
+ when :PM_SOURCE_INIT_ERROR_DIRECTORY
218
245
  raise Errno::EISDIR.new(filepath)
246
+ when :PM_SOURCE_INIT_ERROR_NON_REGULAR
247
+ # Fall back to reading the file through Ruby IO for non-regular
248
+ # files (pipes, character devices, etc.)
249
+ return with_string(File.read(filepath)) { |string| yield string }
219
250
  else
220
- raise "Unknown error initializing pm_string_t: #{result.inspect}"
251
+ raise "Unknown error initializing pm_source_t: #{result_ptr.read_int}"
221
252
  end
222
253
  ensure
223
- LibRubyParser.pm_string_free(pm_string)
254
+ LibRubyParser.pm_source_free(pm_source) if pm_source && !pm_source.null?
224
255
  end
225
256
  end
226
257
  end
@@ -236,47 +267,58 @@ module Prism
236
267
  class << self
237
268
  # Mirror the Prism.dump API by using the serialization API.
238
269
  def dump(source, **options)
239
- LibRubyParser::PrismString.with_string(source) { |string| dump_common(string, options) }
270
+ LibRubyParser::PrismSource.with_string(source) { |string| dump_common(string, options) }
240
271
  end
241
272
 
242
273
  # Mirror the Prism.dump_file API by using the serialization API.
243
274
  def dump_file(filepath, **options)
244
- options[:filepath] = filepath
245
- LibRubyParser::PrismString.with_file(filepath) { |string| dump_common(string, options) }
275
+ LibRubyParser::PrismSource.with_file(filepath, options) { |string| dump_common(string, options) }
246
276
  end
247
277
 
248
278
  # Mirror the Prism.lex API by using the serialization API.
249
279
  def lex(code, **options)
250
- LibRubyParser::PrismString.with_string(code) { |string| lex_common(string, code, options) }
280
+ LibRubyParser::PrismSource.with_string(code) { |string| lex_common(string, code, options) }
251
281
  end
252
282
 
253
283
  # Mirror the Prism.lex_file API by using the serialization API.
254
284
  def lex_file(filepath, **options)
255
- options[:filepath] = filepath
256
- LibRubyParser::PrismString.with_file(filepath) { |string| lex_common(string, string.read, options) }
285
+ LibRubyParser::PrismSource.with_file(filepath, options) { |string| lex_common(string, string.read, options) }
257
286
  end
258
287
 
259
288
  # Mirror the Prism.parse API by using the serialization API.
260
289
  def parse(code, **options)
261
- LibRubyParser::PrismString.with_string(code) { |string| parse_common(string, code, options) }
290
+ LibRubyParser::PrismSource.with_string(code) { |string| parse_common(string, code, options) }
262
291
  end
263
292
 
264
293
  # Mirror the Prism.parse_file API by using the serialization API. This uses
265
294
  # native strings instead of Ruby strings because it allows us to use mmap
266
295
  # when it is available.
267
296
  def parse_file(filepath, **options)
268
- options[:filepath] = filepath
269
- LibRubyParser::PrismString.with_file(filepath) { |string| parse_common(string, string.read, options) }
297
+ LibRubyParser::PrismSource.with_file(filepath, options) { |string| parse_common(string, string.read, options) }
270
298
  end
271
299
 
272
300
  # Mirror the Prism.parse_stream API by using the serialization API.
273
301
  def parse_stream(stream, **options)
302
+ format_type = raise_error_format_type(options)
303
+
274
304
  LibRubyParser::PrismBuffer.with do |buffer|
305
+ # The largest number of bytes a single character can occupy in any
306
+ # encoding Ruby supports. IO#gets(limit) may return up to
307
+ # (max_enc_len - 1) bytes more than requested to avoid splitting a
308
+ # multi-byte character, so we reserve that much headroom (plus the NUL
309
+ # terminator) to guarantee the result fits in the caller's buffer. This
310
+ # mirrors MAX_ENC_LEN in ext/prism/extension.c.
311
+ max_enc_len = 6
312
+
275
313
  source = +""
276
314
  callback = -> (string, size, _) {
277
- raise "Expected size to be >= 0, got: #{size}" if size <= 0
315
+ raise "Expected size to be > #{max_enc_len}, got: #{size}" if size <= max_enc_len
278
316
 
279
- if !(line = stream.gets(size - 1)).nil?
317
+ line = String.try_convert(stream.gets(size - max_enc_len))
318
+ if !line.nil?
319
+ # A misbehaving `gets` may ignore the limit; never write past the
320
+ # buffer (one byte is reserved for the NUL terminator).
321
+ line = line.byteslice(0, size - 1) if line.bytesize > size - 1
280
322
  source << line
281
323
  string.write_string("#{line}\x00", line.bytesize + 1)
282
324
  end
@@ -284,43 +326,49 @@ module Prism
284
326
 
285
327
  eof_callback = -> (_) { stream.eof? }
286
328
 
287
- # In the pm_serialize_parse_stream function it accepts a pointer to the
288
- # IO object as a void* and then passes it through to the callback as the
289
- # third argument, but it never touches it itself. As such, since we have
290
- # access to the IO object already through the closure of the lambda, we
291
- # can pass a null pointer here and not worry.
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))
329
+ pm_source = LibRubyParser.pm_source_stream_new(nil, callback, eof_callback)
330
+ begin
331
+ LibRubyParser.pm_serialize_parse_stream(buffer.pointer, pm_source, dump_options(options))
332
+ result = Prism.load(source, buffer.read, options.fetch(:freeze, false))
333
+
334
+ if format_type && result.failure?
335
+ LibRubyParser::PrismSource.with_string(source) do |string|
336
+ raise_error(string, options, format_type)
337
+ end
338
+ end
339
+
340
+ result
341
+ ensure
342
+ LibRubyParser.pm_source_free(pm_source) if pm_source && !pm_source.null?
343
+ end
294
344
  end
295
345
  end
296
346
 
297
347
  # Mirror the Prism.parse_comments API by using the serialization API.
298
348
  def parse_comments(code, **options)
299
- LibRubyParser::PrismString.with_string(code) { |string| parse_comments_common(string, code, options) }
349
+ LibRubyParser::PrismSource.with_string(code) { |string| parse_comments_common(string, code, options) }
300
350
  end
301
351
 
302
352
  # Mirror the Prism.parse_file_comments API by using the serialization
303
353
  # API. This uses native strings instead of Ruby strings because it allows us
304
354
  # to use mmap when it is available.
305
355
  def parse_file_comments(filepath, **options)
306
- options[:filepath] = filepath
307
- LibRubyParser::PrismString.with_file(filepath) { |string| parse_comments_common(string, string.read, options) }
356
+ LibRubyParser::PrismSource.with_file(filepath, options) { |string| parse_comments_common(string, string.read, options) }
308
357
  end
309
358
 
310
359
  # Mirror the Prism.parse_lex API by using the serialization API.
311
360
  def parse_lex(code, **options)
312
- LibRubyParser::PrismString.with_string(code) { |string| parse_lex_common(string, code, options) }
361
+ LibRubyParser::PrismSource.with_string(code) { |string| parse_lex_common(string, code, options) }
313
362
  end
314
363
 
315
364
  # Mirror the Prism.parse_lex_file API by using the serialization API.
316
365
  def parse_lex_file(filepath, **options)
317
- options[:filepath] = filepath
318
- LibRubyParser::PrismString.with_file(filepath) { |string| parse_lex_common(string, string.read, options) }
366
+ LibRubyParser::PrismSource.with_file(filepath, options) { |string| parse_lex_common(string, string.read, options) }
319
367
  end
320
368
 
321
369
  # Mirror the Prism.parse_success? API by using the serialization API.
322
370
  def parse_success?(code, **options)
323
- LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) }
371
+ LibRubyParser::PrismSource.with_string(code) { |string| parse_file_success_common(string, options) }
324
372
  end
325
373
 
326
374
  # Mirror the Prism.parse_failure? API by using the serialization API.
@@ -330,8 +378,7 @@ module Prism
330
378
 
331
379
  # Mirror the Prism.parse_file_success? API by using the serialization API.
332
380
  def parse_file_success?(filepath, **options)
333
- options[:filepath] = filepath
334
- LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) }
381
+ LibRubyParser::PrismSource.with_file(filepath, options) { |string| parse_file_success_common(string, options) }
335
382
  end
336
383
 
337
384
  # Mirror the Prism.parse_file_failure? API by using the serialization API.
@@ -341,7 +388,11 @@ module Prism
341
388
 
342
389
  # Mirror the Prism.profile API by using the serialization API.
343
390
  def profile(source, **options)
344
- LibRubyParser::PrismString.with_string(source) do |string|
391
+ LibRubyParser::PrismSource.with_string(source) do |string|
392
+ if (format_type = raise_error_format_type(options))
393
+ raise_error(string, options, format_type)
394
+ end
395
+
345
396
  LibRubyParser::PrismBuffer.with do |buffer|
346
397
  LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
347
398
  nil
@@ -351,9 +402,12 @@ module Prism
351
402
 
352
403
  # Mirror the Prism.profile_file API by using the serialization API.
353
404
  def profile_file(filepath, **options)
354
- LibRubyParser::PrismString.with_file(filepath) do |string|
405
+ LibRubyParser::PrismSource.with_file(filepath, options) do |string|
406
+ if (format_type = raise_error_format_type(options))
407
+ raise_error(string, options, format_type)
408
+ end
409
+
355
410
  LibRubyParser::PrismBuffer.with do |buffer|
356
- options[:filepath] = filepath
357
411
  LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
358
412
  nil
359
413
  end
@@ -363,6 +417,10 @@ module Prism
363
417
  private
364
418
 
365
419
  def dump_common(string, options) # :nodoc:
420
+ if (format_type = raise_error_format_type(options))
421
+ raise_error(string, options, format_type)
422
+ end
423
+
366
424
  LibRubyParser::PrismBuffer.with do |buffer|
367
425
  LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
368
426
 
@@ -374,18 +432,31 @@ module Prism
374
432
  end
375
433
 
376
434
  def lex_common(string, code, options) # :nodoc:
435
+ format_type = raise_error_format_type(options)
436
+
377
437
  LibRubyParser::PrismBuffer.with do |buffer|
378
438
  LibRubyParser.pm_serialize_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
379
- Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false))
439
+ result = Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false))
440
+
441
+ raise_error(string, options, format_type) if format_type && result.failure?
442
+ result
380
443
  end
381
444
  end
382
445
 
383
446
  def parse_common(string, code, options) # :nodoc:
447
+ format_type = raise_error_format_type(options)
384
448
  serialized = dump_common(string, options)
385
- Serialize.load_parse(code, serialized, options.fetch(:freeze, false))
449
+ result = Serialize.load_parse(code, serialized, options.fetch(:freeze, false))
450
+
451
+ raise_error(string, options, format_type) if format_type && result.failure?
452
+ result
386
453
  end
387
454
 
388
455
  def parse_comments_common(string, code, options) # :nodoc:
456
+ if (format_type = raise_error_format_type(options))
457
+ raise_error(string, options, format_type)
458
+ end
459
+
389
460
  LibRubyParser::PrismBuffer.with do |buffer|
390
461
  LibRubyParser.pm_serialize_parse_comments(buffer.pointer, string.pointer, string.length, dump_options(options))
391
462
  Serialize.load_parse_comments(code, buffer.read, options.fetch(:freeze, false))
@@ -393,14 +464,82 @@ module Prism
393
464
  end
394
465
 
395
466
  def parse_lex_common(string, code, options) # :nodoc:
467
+ format_type = raise_error_format_type(options)
468
+
396
469
  LibRubyParser::PrismBuffer.with do |buffer|
397
470
  LibRubyParser.pm_serialize_parse_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
398
- Serialize.load_parse_lex(code, buffer.read, options.fetch(:freeze, false))
471
+ result = Serialize.load_parse_lex(code, buffer.read, options.fetch(:freeze, false))
472
+
473
+ raise_error(string, options, format_type) if format_type && result.failure?
474
+ result
399
475
  end
400
476
  end
401
477
 
402
478
  def parse_file_success_common(string, options) # :nodoc:
403
- LibRubyParser.pm_parse_success_p(string.pointer, string.length, dump_options(options))
479
+ format_type = raise_error_format_type(options)
480
+ success = LibRubyParser.pm_serialize_parse_success_p(string.pointer, string.length, dump_options(options))
481
+
482
+ raise_error(string, options, format_type) if format_type && !success
483
+ success
484
+ end
485
+
486
+ # Extract the raise_error option from the given options hash and convert
487
+ # it into the format type that should be used when formatting errors, or
488
+ # nil if raising is disabled.
489
+ def raise_error_format_type(options) # :nodoc:
490
+ case (value = options.delete(:raise_error))
491
+ when nil, false
492
+ nil
493
+ when true
494
+ # When given true, mirror the behavior of CRuby itself: color when
495
+ # $stderr is a terminal (unless NO_COLOR is set), bold styling when
496
+ # NO_COLOR is set, plain otherwise. This policy is duplicated in
497
+ # ext/prism/extension.c (build_options_i) and the two must stay in
498
+ # sync.
499
+ if $stderr.respond_to?(:tty?) && $stderr.tty?
500
+ no_color = ENV["NO_COLOR"]
501
+ (no_color.nil? || no_color.empty?) ? :PM_ERRORS_FORMAT_COLOR : :PM_ERRORS_FORMAT_STYLE
502
+ else
503
+ :PM_ERRORS_FORMAT_PLAIN
504
+ end
505
+ when :plain
506
+ :PM_ERRORS_FORMAT_PLAIN
507
+ when :style
508
+ :PM_ERRORS_FORMAT_STYLE
509
+ when :color
510
+ :PM_ERRORS_FORMAT_COLOR
511
+ when Symbol
512
+ raise ArgumentError, "invalid raise_error value: #{value}"
513
+ else
514
+ raise TypeError, "wrong argument type #{value.class} (expected Symbol)"
515
+ end
516
+ end
517
+
518
+ # Parse the given source and format any errors that are encountered into
519
+ # an appropriate exception, then raise it. If the source parses without
520
+ # any errors, then return nil.
521
+ def raise_error(string, options, format_type) # :nodoc:
522
+ LibRubyParser::PrismBuffer.with do |buffer|
523
+ level = LibRubyParser.pm_serialize_parse_errors_format(buffer.pointer, string.pointer, string.length, dump_options(options), format_type)
524
+ return if level == -1
525
+
526
+ encoding_name, _, message = buffer.read.partition("\0")
527
+
528
+ case level
529
+ when 0 # syntax
530
+ error = SyntaxError.new(message.force_encoding(encoding_name))
531
+ error.instance_variable_set(:@path, options.fetch(:filepath, ""))
532
+ raise error
533
+ when 1 # argument
534
+ raise ArgumentError, message.force_encoding(encoding_name)
535
+ when 2 # load
536
+ error = LoadError.new(message.force_encoding(Encoding.find("locale")))
537
+ error.instance_variable_set(:@path, nil)
538
+ raise error
539
+ else
540
+ raise "Unknown error level: #{level}"
541
+ end
542
+ end
404
543
  end
405
544
 
406
545
  # Return the value that should be dumped for the command_line option.
@@ -423,11 +562,28 @@ module Prism
423
562
 
424
563
  # Return the value that should be dumped for the version option.
425
564
  def dump_options_version(version)
426
- current = version == "current"
427
-
428
- case current ? RUBY_VERSION : version
429
- when nil, "latest"
565
+ case version
566
+ when "current"
567
+ version_string_to_number(RUBY_VERSION) || raise(CurrentVersionError, RUBY_VERSION)
568
+ when "latest"
430
569
  0 # Handled in pm_parser_init
570
+ when "nearest", nil
571
+ dump = version_string_to_number(RUBY_VERSION)
572
+ return dump if dump
573
+ if RUBY_VERSION < "3.3"
574
+ version_string_to_number("3.3")
575
+ else
576
+ 0 # Handled in pm_parser_init
577
+ end
578
+ else
579
+ version_string_to_number(version) || raise(ArgumentError, "invalid version: #{version}")
580
+ end
581
+ end
582
+
583
+ # Converts a version string like "4.0.0" or "4.0" into a number.
584
+ # Returns nil if the version is unknown.
585
+ def version_string_to_number(version)
586
+ case version
431
587
  when /\A3\.3(\.\d+)?\z/
432
588
  1
433
589
  when /\A3\.4(\.\d+)?\z/
@@ -436,17 +592,20 @@ module Prism
436
592
  3
437
593
  when /\A4\.1(\.\d+)?\z/
438
594
  4
439
- else
440
- if current
441
- raise CurrentVersionError, RUBY_VERSION
442
- else
443
- raise ArgumentError, "invalid version: #{version}"
444
- end
445
595
  end
446
596
  end
447
597
 
598
+ # The set of options that are understood by the parsing APIs. Note that
599
+ # raise_error is not listed here because it is deleted from the options
600
+ # hash by raise_error_format_type before the options are dumped.
601
+ DUMP_OPTIONS_KEYS = [:attach_comments, :command_line, :encoding, :filepath, :freeze, :frozen_string_literal, :line, :main_script, :partial_script, :scopes, :version].freeze
602
+ private_constant :DUMP_OPTIONS_KEYS
603
+
448
604
  # Convert the given options into a serialized options string.
449
605
  def dump_options(options)
606
+ unknown_keys = options.keys - DUMP_OPTIONS_KEYS
607
+ raise ArgumentError, "unknown keyword: #{unknown_keys.first}" unless unknown_keys.empty?
608
+
450
609
  template = +""
451
610
  values = []
452
611
 
@@ -491,6 +650,9 @@ module Prism
491
650
  template << "C"
492
651
  values << (options.fetch(:freeze, false) ? 1 : 0)
493
652
 
653
+ template << "C"
654
+ values << (options.fetch(:attach_comments, false) ? 1 : 0)
655
+
494
656
  template << "L"
495
657
  if (scopes = options[:scopes])
496
658
  values << scopes.length
@@ -543,7 +705,7 @@ module Prism
543
705
 
544
706
  # Here we are going to patch StringQuery to put in the class-level methods so
545
707
  # that it can maintain a consistent interface
546
- class StringQuery
708
+ class StringQuery # :nodoc:
547
709
  class << self
548
710
  # Mirrors the C extension's StringQuery::local? method.
549
711
  def local?(string)