ed-precompiled_prism 1.5.2

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 (154) hide show
  1. checksums.yaml +7 -0
  2. data/BSDmakefile +58 -0
  3. data/CHANGELOG.md +723 -0
  4. data/CODE_OF_CONDUCT.md +76 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/LICENSE.md +7 -0
  7. data/Makefile +110 -0
  8. data/README.md +143 -0
  9. data/config.yml +4714 -0
  10. data/docs/build_system.md +119 -0
  11. data/docs/configuration.md +68 -0
  12. data/docs/cruby_compilation.md +27 -0
  13. data/docs/design.md +53 -0
  14. data/docs/encoding.md +121 -0
  15. data/docs/fuzzing.md +88 -0
  16. data/docs/heredocs.md +36 -0
  17. data/docs/javascript.md +118 -0
  18. data/docs/local_variable_depth.md +229 -0
  19. data/docs/mapping.md +117 -0
  20. data/docs/parser_translation.md +24 -0
  21. data/docs/parsing_rules.md +22 -0
  22. data/docs/releasing.md +98 -0
  23. data/docs/relocation.md +34 -0
  24. data/docs/ripper_translation.md +72 -0
  25. data/docs/ruby_api.md +44 -0
  26. data/docs/ruby_parser_translation.md +19 -0
  27. data/docs/serialization.md +233 -0
  28. data/docs/testing.md +55 -0
  29. data/ext/prism/api_node.c +6941 -0
  30. data/ext/prism/api_pack.c +276 -0
  31. data/ext/prism/extconf.rb +127 -0
  32. data/ext/prism/extension.c +1419 -0
  33. data/ext/prism/extension.h +19 -0
  34. data/include/prism/ast.h +8220 -0
  35. data/include/prism/defines.h +260 -0
  36. data/include/prism/diagnostic.h +456 -0
  37. data/include/prism/encoding.h +283 -0
  38. data/include/prism/node.h +129 -0
  39. data/include/prism/options.h +482 -0
  40. data/include/prism/pack.h +163 -0
  41. data/include/prism/parser.h +933 -0
  42. data/include/prism/prettyprint.h +34 -0
  43. data/include/prism/regexp.h +43 -0
  44. data/include/prism/static_literals.h +121 -0
  45. data/include/prism/util/pm_buffer.h +236 -0
  46. data/include/prism/util/pm_char.h +204 -0
  47. data/include/prism/util/pm_constant_pool.h +218 -0
  48. data/include/prism/util/pm_integer.h +130 -0
  49. data/include/prism/util/pm_list.h +103 -0
  50. data/include/prism/util/pm_memchr.h +29 -0
  51. data/include/prism/util/pm_newline_list.h +113 -0
  52. data/include/prism/util/pm_string.h +200 -0
  53. data/include/prism/util/pm_strncasecmp.h +32 -0
  54. data/include/prism/util/pm_strpbrk.h +46 -0
  55. data/include/prism/version.h +29 -0
  56. data/include/prism.h +408 -0
  57. data/lib/prism/compiler.rb +801 -0
  58. data/lib/prism/desugar_compiler.rb +392 -0
  59. data/lib/prism/dispatcher.rb +2210 -0
  60. data/lib/prism/dot_visitor.rb +4762 -0
  61. data/lib/prism/dsl.rb +1003 -0
  62. data/lib/prism/ffi.rb +570 -0
  63. data/lib/prism/inspect_visitor.rb +2392 -0
  64. data/lib/prism/lex_compat.rb +928 -0
  65. data/lib/prism/mutation_compiler.rb +772 -0
  66. data/lib/prism/node.rb +18816 -0
  67. data/lib/prism/node_ext.rb +511 -0
  68. data/lib/prism/pack.rb +230 -0
  69. data/lib/prism/parse_result/comments.rb +188 -0
  70. data/lib/prism/parse_result/errors.rb +66 -0
  71. data/lib/prism/parse_result/newlines.rb +155 -0
  72. data/lib/prism/parse_result.rb +911 -0
  73. data/lib/prism/pattern.rb +269 -0
  74. data/lib/prism/polyfill/append_as_bytes.rb +15 -0
  75. data/lib/prism/polyfill/byteindex.rb +13 -0
  76. data/lib/prism/polyfill/scan_byte.rb +14 -0
  77. data/lib/prism/polyfill/unpack1.rb +14 -0
  78. data/lib/prism/polyfill/warn.rb +36 -0
  79. data/lib/prism/reflection.rb +416 -0
  80. data/lib/prism/relocation.rb +505 -0
  81. data/lib/prism/serialize.rb +2398 -0
  82. data/lib/prism/string_query.rb +31 -0
  83. data/lib/prism/translation/parser/builder.rb +62 -0
  84. data/lib/prism/translation/parser/compiler.rb +2234 -0
  85. data/lib/prism/translation/parser/lexer.rb +820 -0
  86. data/lib/prism/translation/parser.rb +374 -0
  87. data/lib/prism/translation/parser33.rb +13 -0
  88. data/lib/prism/translation/parser34.rb +13 -0
  89. data/lib/prism/translation/parser35.rb +13 -0
  90. data/lib/prism/translation/parser_current.rb +24 -0
  91. data/lib/prism/translation/ripper/sexp.rb +126 -0
  92. data/lib/prism/translation/ripper/shim.rb +5 -0
  93. data/lib/prism/translation/ripper.rb +3474 -0
  94. data/lib/prism/translation/ruby_parser.rb +1929 -0
  95. data/lib/prism/translation.rb +16 -0
  96. data/lib/prism/visitor.rb +813 -0
  97. data/lib/prism.rb +97 -0
  98. data/prism.gemspec +174 -0
  99. data/rbi/prism/compiler.rbi +12 -0
  100. data/rbi/prism/dsl.rbi +524 -0
  101. data/rbi/prism/inspect_visitor.rbi +12 -0
  102. data/rbi/prism/node.rbi +8734 -0
  103. data/rbi/prism/node_ext.rbi +107 -0
  104. data/rbi/prism/parse_result.rbi +404 -0
  105. data/rbi/prism/reflection.rbi +58 -0
  106. data/rbi/prism/string_query.rbi +12 -0
  107. data/rbi/prism/translation/parser.rbi +11 -0
  108. data/rbi/prism/translation/parser33.rbi +6 -0
  109. data/rbi/prism/translation/parser34.rbi +6 -0
  110. data/rbi/prism/translation/parser35.rbi +6 -0
  111. data/rbi/prism/translation/ripper.rbi +15 -0
  112. data/rbi/prism/visitor.rbi +473 -0
  113. data/rbi/prism.rbi +66 -0
  114. data/sig/prism/compiler.rbs +9 -0
  115. data/sig/prism/dispatcher.rbs +19 -0
  116. data/sig/prism/dot_visitor.rbs +6 -0
  117. data/sig/prism/dsl.rbs +351 -0
  118. data/sig/prism/inspect_visitor.rbs +22 -0
  119. data/sig/prism/lex_compat.rbs +10 -0
  120. data/sig/prism/mutation_compiler.rbs +159 -0
  121. data/sig/prism/node.rbs +4028 -0
  122. data/sig/prism/node_ext.rbs +149 -0
  123. data/sig/prism/pack.rbs +43 -0
  124. data/sig/prism/parse_result/comments.rbs +38 -0
  125. data/sig/prism/parse_result.rbs +196 -0
  126. data/sig/prism/pattern.rbs +13 -0
  127. data/sig/prism/reflection.rbs +50 -0
  128. data/sig/prism/relocation.rbs +185 -0
  129. data/sig/prism/serialize.rbs +8 -0
  130. data/sig/prism/string_query.rbs +11 -0
  131. data/sig/prism/visitor.rbs +169 -0
  132. data/sig/prism.rbs +254 -0
  133. data/src/diagnostic.c +850 -0
  134. data/src/encoding.c +5235 -0
  135. data/src/node.c +8676 -0
  136. data/src/options.c +328 -0
  137. data/src/pack.c +509 -0
  138. data/src/prettyprint.c +8941 -0
  139. data/src/prism.c +23361 -0
  140. data/src/regexp.c +790 -0
  141. data/src/serialize.c +2268 -0
  142. data/src/static_literals.c +617 -0
  143. data/src/token_type.c +703 -0
  144. data/src/util/pm_buffer.c +357 -0
  145. data/src/util/pm_char.c +318 -0
  146. data/src/util/pm_constant_pool.c +342 -0
  147. data/src/util/pm_integer.c +670 -0
  148. data/src/util/pm_list.c +49 -0
  149. data/src/util/pm_memchr.c +35 -0
  150. data/src/util/pm_newline_list.c +125 -0
  151. data/src/util/pm_string.c +381 -0
  152. data/src/util/pm_strncasecmp.c +36 -0
  153. data/src/util/pm_strpbrk.c +206 -0
  154. metadata +195 -0
data/lib/prism/ffi.rb ADDED
@@ -0,0 +1,570 @@
1
+ # frozen_string_literal: true
2
+ # :markup: markdown
3
+ # typed: ignore
4
+
5
+ # This file is responsible for mirroring the API provided by the C extension by
6
+ # using FFI to call into the shared library.
7
+
8
+ require "rbconfig"
9
+ require "ffi"
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
+
15
+ module Prism
16
+ module LibRubyParser # :nodoc:
17
+ extend FFI::Library
18
+
19
+ # Define the library that we will be pulling functions from. Note that this
20
+ # must align with the build shared library from make/rake.
21
+ libprism_in_build = File.expand_path("../../build/libprism.#{RbConfig::CONFIG["SOEXT"]}", __dir__)
22
+ libprism_in_libdir = "#{RbConfig::CONFIG["libdir"]}/prism/libprism.#{RbConfig::CONFIG["SOEXT"]}"
23
+
24
+ if File.exist?(libprism_in_build)
25
+ INCLUDE_DIR = File.expand_path("../../include", __dir__)
26
+ ffi_lib libprism_in_build
27
+ else
28
+ INCLUDE_DIR = "#{RbConfig::CONFIG["libdir"]}/prism/include"
29
+ ffi_lib libprism_in_libdir
30
+ end
31
+
32
+ # Convert a native C type declaration into a symbol that FFI understands.
33
+ # For example:
34
+ #
35
+ # const char * -> :pointer
36
+ # bool -> :bool
37
+ # size_t -> :size_t
38
+ # void -> :void
39
+ #
40
+ def self.resolve_type(type, callbacks)
41
+ type = type.strip
42
+
43
+ if !type.end_with?("*")
44
+ type.delete_prefix("const ").to_sym
45
+ else
46
+ type = type.delete_suffix("*").rstrip
47
+ callbacks.include?(type.to_sym) ? type.to_sym : :pointer
48
+ end
49
+ end
50
+
51
+ # Read through the given header file and find the declaration of each of the
52
+ # given functions. For each one, define a function with the same name and
53
+ # signature as the C function.
54
+ def self.load_exported_functions_from(header, *functions, callbacks)
55
+ File.foreach("#{INCLUDE_DIR}/#{header}") do |line|
56
+ # We only want to attempt to load exported functions.
57
+ next unless line.start_with?("PRISM_EXPORTED_FUNCTION ")
58
+
59
+ # We only want to load the functions that we are interested in.
60
+ next unless functions.any? { |function| line.include?(function) }
61
+
62
+ # Parse the function declaration.
63
+ unless /^PRISM_EXPORTED_FUNCTION (?<return_type>.+) (?<name>\w+)\((?<arg_types>.+)\);$/ =~ line
64
+ raise "Could not parse #{line}"
65
+ end
66
+
67
+ # Delete the function from the list of functions we are looking for to
68
+ # mark it as having been found.
69
+ functions.delete(name)
70
+
71
+ # Split up the argument types into an array, ensure we handle the case
72
+ # where there are no arguments (by explicit void).
73
+ arg_types = arg_types.split(",").map(&:strip)
74
+ arg_types = [] if arg_types == %w[void]
75
+
76
+ # Resolve the type of the argument by dropping the name of the argument
77
+ # first if it is present.
78
+ arg_types.map! { |type| resolve_type(type.sub(/\w+$/, ""), callbacks) }
79
+
80
+ # Attach the function using the FFI library.
81
+ attach_function name, arg_types, resolve_type(return_type, [])
82
+ end
83
+
84
+ # If we didn't find all of the functions, raise an error.
85
+ raise "Could not find functions #{functions.inspect}" unless functions.empty?
86
+ end
87
+
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]
91
+ enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE]
92
+
93
+ load_exported_functions_from(
94
+ "prism.h",
95
+ "pm_version",
96
+ "pm_serialize_parse",
97
+ "pm_serialize_parse_stream",
98
+ "pm_serialize_parse_comments",
99
+ "pm_serialize_lex",
100
+ "pm_serialize_parse_lex",
101
+ "pm_parse_success_p",
102
+ "pm_string_query_local",
103
+ "pm_string_query_constant",
104
+ "pm_string_query_method_name",
105
+ [:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t]
106
+ )
107
+
108
+ load_exported_functions_from(
109
+ "prism/util/pm_buffer.h",
110
+ "pm_buffer_sizeof",
111
+ "pm_buffer_init",
112
+ "pm_buffer_value",
113
+ "pm_buffer_length",
114
+ "pm_buffer_free",
115
+ []
116
+ )
117
+
118
+ 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
+ []
126
+ )
127
+
128
+ # This object represents a pm_buffer_t. We only use it as an opaque pointer,
129
+ # so it doesn't need to know the fields of pm_buffer_t.
130
+ class PrismBuffer # :nodoc:
131
+ SIZEOF = LibRubyParser.pm_buffer_sizeof
132
+
133
+ attr_reader :pointer
134
+
135
+ def initialize(pointer)
136
+ @pointer = pointer
137
+ end
138
+
139
+ def value
140
+ LibRubyParser.pm_buffer_value(pointer)
141
+ end
142
+
143
+ def length
144
+ LibRubyParser.pm_buffer_length(pointer)
145
+ end
146
+
147
+ def read
148
+ value.read_string(length)
149
+ end
150
+
151
+ # Initialize a new buffer and yield it to the block. The buffer will be
152
+ # automatically freed when the block returns.
153
+ def self.with
154
+ FFI::MemoryPointer.new(SIZEOF) do |pointer|
155
+ raise unless LibRubyParser.pm_buffer_init(pointer)
156
+ return yield new(pointer)
157
+ ensure
158
+ LibRubyParser.pm_buffer_free(pointer)
159
+ end
160
+ end
161
+ end
162
+
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
+
168
+ PLATFORM_EXPECTS_UTF8 =
169
+ RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i)
170
+
171
+ attr_reader :pointer, :length
172
+
173
+ def initialize(pointer, length, from_string)
174
+ @pointer = pointer
175
+ @length = length
176
+ @from_string = from_string
177
+ end
178
+
179
+ def read
180
+ raise "should use the original String instead" if @from_string
181
+ @pointer.read_string(@length)
182
+ end
183
+
184
+ # Yields a pm_string_t pointer to the given block.
185
+ def self.with_string(string)
186
+ raise TypeError unless string.is_a?(String)
187
+
188
+ length = string.bytesize
189
+ # + 1 to never get an address of 0, which pm_parser_init() asserts
190
+ FFI::MemoryPointer.new(:char, length + 1, false) do |pointer|
191
+ pointer.write_string(string)
192
+ # since we have the extra byte we might as well \0-terminate
193
+ pointer.put_char(length, 0)
194
+ return yield new(pointer, length, true)
195
+ end
196
+ end
197
+
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)
201
+
202
+ # On Windows and Mac, it's expected that filepaths will be encoded in
203
+ # UTF-8. If they are not, we need to convert them to UTF-8 before
204
+ # passing them into pm_string_mapped_init.
205
+ if PLATFORM_EXPECTS_UTF8 && (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8
206
+ filepath = filepath.encode(Encoding::UTF_8)
207
+ end
208
+
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)
214
+ return yield new(pointer, length, false)
215
+ when :PM_STRING_INIT_ERROR_GENERIC
216
+ raise SystemCallError.new(filepath, FFI.errno)
217
+ when :PM_STRING_INIT_ERROR_DIRECTORY
218
+ raise Errno::EISDIR.new(filepath)
219
+ else
220
+ raise "Unknown error initializing pm_string_t: #{result.inspect}"
221
+ end
222
+ ensure
223
+ LibRubyParser.pm_string_free(pm_string)
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ # Mark the LibRubyParser module as private as it should only be called through
230
+ # the prism module.
231
+ private_constant :LibRubyParser
232
+
233
+ # The version constant is set by reading the result of calling pm_version.
234
+ VERSION = LibRubyParser.pm_version.read_string.freeze
235
+
236
+ class << self
237
+ # Mirror the Prism.dump API by using the serialization API.
238
+ def dump(source, **options)
239
+ LibRubyParser::PrismString.with_string(source) { |string| dump_common(string, options) }
240
+ end
241
+
242
+ # Mirror the Prism.dump_file API by using the serialization API.
243
+ def dump_file(filepath, **options)
244
+ options[:filepath] = filepath
245
+ LibRubyParser::PrismString.with_file(filepath) { |string| dump_common(string, options) }
246
+ end
247
+
248
+ # Mirror the Prism.lex API by using the serialization API.
249
+ def lex(code, **options)
250
+ LibRubyParser::PrismString.with_string(code) { |string| lex_common(string, code, options) }
251
+ end
252
+
253
+ # Mirror the Prism.lex_file API by using the serialization API.
254
+ def lex_file(filepath, **options)
255
+ options[:filepath] = filepath
256
+ LibRubyParser::PrismString.with_file(filepath) { |string| lex_common(string, string.read, options) }
257
+ end
258
+
259
+ # Mirror the Prism.parse API by using the serialization API.
260
+ def parse(code, **options)
261
+ LibRubyParser::PrismString.with_string(code) { |string| parse_common(string, code, options) }
262
+ end
263
+
264
+ # Mirror the Prism.parse_file API by using the serialization API. This uses
265
+ # native strings instead of Ruby strings because it allows us to use mmap
266
+ # when it is available.
267
+ def parse_file(filepath, **options)
268
+ options[:filepath] = filepath
269
+ LibRubyParser::PrismString.with_file(filepath) { |string| parse_common(string, string.read, options) }
270
+ end
271
+
272
+ # Mirror the Prism.parse_stream API by using the serialization API.
273
+ def parse_stream(stream, **options)
274
+ LibRubyParser::PrismBuffer.with do |buffer|
275
+ source = +""
276
+ callback = -> (string, size, _) {
277
+ raise "Expected size to be >= 0, got: #{size}" if size <= 0
278
+
279
+ if !(line = stream.gets(size - 1)).nil?
280
+ source << line
281
+ string.write_string("#{line}\x00", line.bytesize + 1)
282
+ end
283
+ }
284
+
285
+ eof_callback = -> (_) { stream.eof? }
286
+
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))
294
+ end
295
+ end
296
+
297
+ # Mirror the Prism.parse_comments API by using the serialization API.
298
+ def parse_comments(code, **options)
299
+ LibRubyParser::PrismString.with_string(code) { |string| parse_comments_common(string, code, options) }
300
+ end
301
+
302
+ # Mirror the Prism.parse_file_comments API by using the serialization
303
+ # API. This uses native strings instead of Ruby strings because it allows us
304
+ # to use mmap when it is available.
305
+ def parse_file_comments(filepath, **options)
306
+ options[:filepath] = filepath
307
+ LibRubyParser::PrismString.with_file(filepath) { |string| parse_comments_common(string, string.read, options) }
308
+ end
309
+
310
+ # Mirror the Prism.parse_lex API by using the serialization API.
311
+ def parse_lex(code, **options)
312
+ LibRubyParser::PrismString.with_string(code) { |string| parse_lex_common(string, code, options) }
313
+ end
314
+
315
+ # Mirror the Prism.parse_lex_file API by using the serialization API.
316
+ def parse_lex_file(filepath, **options)
317
+ options[:filepath] = filepath
318
+ LibRubyParser::PrismString.with_file(filepath) { |string| parse_lex_common(string, string.read, options) }
319
+ end
320
+
321
+ # Mirror the Prism.parse_success? API by using the serialization API.
322
+ def parse_success?(code, **options)
323
+ LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) }
324
+ end
325
+
326
+ # Mirror the Prism.parse_failure? API by using the serialization API.
327
+ def parse_failure?(code, **options)
328
+ !parse_success?(code, **options)
329
+ end
330
+
331
+ # Mirror the Prism.parse_file_success? API by using the serialization API.
332
+ def parse_file_success?(filepath, **options)
333
+ options[:filepath] = filepath
334
+ LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) }
335
+ end
336
+
337
+ # Mirror the Prism.parse_file_failure? API by using the serialization API.
338
+ def parse_file_failure?(filepath, **options)
339
+ !parse_file_success?(filepath, **options)
340
+ end
341
+
342
+ # Mirror the Prism.profile API by using the serialization API.
343
+ def profile(source, **options)
344
+ LibRubyParser::PrismString.with_string(source) do |string|
345
+ LibRubyParser::PrismBuffer.with do |buffer|
346
+ LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
347
+ nil
348
+ end
349
+ end
350
+ end
351
+
352
+ # Mirror the Prism.profile_file API by using the serialization API.
353
+ def profile_file(filepath, **options)
354
+ LibRubyParser::PrismString.with_file(filepath) do |string|
355
+ LibRubyParser::PrismBuffer.with do |buffer|
356
+ options[:filepath] = filepath
357
+ LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
358
+ nil
359
+ end
360
+ end
361
+ end
362
+
363
+ private
364
+
365
+ def dump_common(string, options) # :nodoc:
366
+ LibRubyParser::PrismBuffer.with do |buffer|
367
+ LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
368
+
369
+ dumped = buffer.read
370
+ dumped.freeze if options.fetch(:freeze, false)
371
+
372
+ dumped
373
+ end
374
+ end
375
+
376
+ def lex_common(string, code, options) # :nodoc:
377
+ LibRubyParser::PrismBuffer.with do |buffer|
378
+ LibRubyParser.pm_serialize_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
379
+ Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false))
380
+ end
381
+ end
382
+
383
+ def parse_common(string, code, options) # :nodoc:
384
+ serialized = dump_common(string, options)
385
+ Serialize.load_parse(code, serialized, options.fetch(:freeze, false))
386
+ end
387
+
388
+ def parse_comments_common(string, code, options) # :nodoc:
389
+ LibRubyParser::PrismBuffer.with do |buffer|
390
+ LibRubyParser.pm_serialize_parse_comments(buffer.pointer, string.pointer, string.length, dump_options(options))
391
+ Serialize.load_parse_comments(code, buffer.read, options.fetch(:freeze, false))
392
+ end
393
+ end
394
+
395
+ def parse_lex_common(string, code, options) # :nodoc:
396
+ LibRubyParser::PrismBuffer.with do |buffer|
397
+ 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))
399
+ end
400
+ end
401
+
402
+ def parse_file_success_common(string, options) # :nodoc:
403
+ LibRubyParser.pm_parse_success_p(string.pointer, string.length, dump_options(options))
404
+ end
405
+
406
+ # Return the value that should be dumped for the command_line option.
407
+ def dump_options_command_line(options)
408
+ command_line = options.fetch(:command_line, "")
409
+ raise ArgumentError, "command_line must be a string" unless command_line.is_a?(String)
410
+
411
+ command_line.each_char.inject(0) do |value, char|
412
+ case char
413
+ when "a" then value | 0b000001
414
+ when "e" then value | 0b000010
415
+ when "l" then value | 0b000100
416
+ when "n" then value | 0b001000
417
+ when "p" then value | 0b010000
418
+ when "x" then value | 0b100000
419
+ else raise ArgumentError, "invalid command_line option: #{char}"
420
+ end
421
+ end
422
+ end
423
+
424
+ # Return the value that should be dumped for the version option.
425
+ def dump_options_version(version)
426
+ case version
427
+ when nil, "latest"
428
+ 0 # Handled in pm_parser_init
429
+ when /\A3\.3(\.\d+)?\z/
430
+ 1
431
+ when /\A3\.4(\.\d+)?\z/
432
+ 2
433
+ when /\A3\.5(\.\d+)?\z/
434
+ 3
435
+ else
436
+ raise ArgumentError, "invalid version: #{version}"
437
+ end
438
+ end
439
+
440
+ # Convert the given options into a serialized options string.
441
+ def dump_options(options)
442
+ template = +""
443
+ values = []
444
+
445
+ template << "L"
446
+ if (filepath = options[:filepath])
447
+ values.push(filepath.bytesize, filepath.b)
448
+ template << "A*"
449
+ else
450
+ values << 0
451
+ end
452
+
453
+ template << "l"
454
+ values << options.fetch(:line, 1)
455
+
456
+ template << "L"
457
+ if (encoding = options[:encoding])
458
+ name = encoding.is_a?(Encoding) ? encoding.name : encoding
459
+ values.push(name.bytesize, name.b)
460
+ template << "A*"
461
+ else
462
+ values << 0
463
+ end
464
+
465
+ template << "C"
466
+ values << (options.fetch(:frozen_string_literal, false) ? 1 : 0)
467
+
468
+ template << "C"
469
+ values << dump_options_command_line(options)
470
+
471
+ template << "C"
472
+ values << dump_options_version(options[:version])
473
+
474
+ template << "C"
475
+ values << (options[:encoding] == false ? 1 : 0)
476
+
477
+ template << "C"
478
+ values << (options.fetch(:main_script, false) ? 1 : 0)
479
+
480
+ template << "C"
481
+ values << (options.fetch(:partial_script, false) ? 1 : 0)
482
+
483
+ template << "C"
484
+ values << (options.fetch(:freeze, false) ? 1 : 0)
485
+
486
+ template << "L"
487
+ if (scopes = options[:scopes])
488
+ values << scopes.length
489
+
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
+
513
+ template << "L"
514
+ values << locals.length
515
+
516
+ template << "C"
517
+ values << forwarding
518
+
519
+ locals.each do |local|
520
+ name = local.name
521
+ template << "L"
522
+ values << name.bytesize
523
+
524
+ template << "A*"
525
+ values << name.b
526
+ end
527
+ end
528
+ else
529
+ values << 0
530
+ end
531
+
532
+ values.pack(template)
533
+ end
534
+ end
535
+
536
+ # Here we are going to patch StringQuery to put in the class-level methods so
537
+ # that it can maintain a consistent interface
538
+ class StringQuery
539
+ class << self
540
+ # Mirrors the C extension's StringQuery::local? method.
541
+ def local?(string)
542
+ query(LibRubyParser.pm_string_query_local(string, string.bytesize, string.encoding.name))
543
+ end
544
+
545
+ # Mirrors the C extension's StringQuery::constant? method.
546
+ def constant?(string)
547
+ query(LibRubyParser.pm_string_query_constant(string, string.bytesize, string.encoding.name))
548
+ end
549
+
550
+ # Mirrors the C extension's StringQuery::method_name? method.
551
+ def method_name?(string)
552
+ query(LibRubyParser.pm_string_query_method_name(string, string.bytesize, string.encoding.name))
553
+ end
554
+
555
+ private
556
+
557
+ # Parse the enum result and return an appropriate boolean.
558
+ def query(result)
559
+ case result
560
+ when :PM_STRING_QUERY_ERROR
561
+ raise ArgumentError, "Invalid or non ascii-compatible encoding"
562
+ when :PM_STRING_QUERY_FALSE
563
+ false
564
+ when :PM_STRING_QUERY_TRUE
565
+ true
566
+ end
567
+ end
568
+ end
569
+ end
570
+ end