prism 1.9.0 → 2.0.0.rc2

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