prism 2.0.0.rc1-x86_64-linux-gnu

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 (216) hide show
  1. checksums.yaml +7 -0
  2. data/BSDmakefile +58 -0
  3. data/CHANGELOG.md +870 -0
  4. data/CODE_OF_CONDUCT.md +76 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/LICENSE.md +7 -0
  7. data/Makefile +145 -0
  8. data/README.md +81 -0
  9. data/config.yml +4752 -0
  10. data/docs/build_system.md +119 -0
  11. data/docs/configuration.md +69 -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 +81 -0
  16. data/docs/heredocs.md +36 -0
  17. data/docs/javascript.md +133 -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 +147 -0
  23. data/docs/relocation.md +32 -0
  24. data/docs/ripper_translation.md +63 -0
  25. data/docs/ruby_api.md +45 -0
  26. data/docs/ruby_parser_translation.md +19 -0
  27. data/docs/serialization.md +230 -0
  28. data/docs/testing.md +72 -0
  29. data/ext/prism/api_node.c +7005 -0
  30. data/ext/prism/extconf.rb +126 -0
  31. data/ext/prism/extension.c +1783 -0
  32. data/ext/prism/extension.h +19 -0
  33. data/include/prism/arena.h +37 -0
  34. data/include/prism/ast.h +10371 -0
  35. data/include/prism/buffer.h +52 -0
  36. data/include/prism/comments.h +55 -0
  37. data/include/prism/compiler/accel.h +24 -0
  38. data/include/prism/compiler/align.h +36 -0
  39. data/include/prism/compiler/assume.h +25 -0
  40. data/include/prism/compiler/exported.h +24 -0
  41. data/include/prism/compiler/fallthrough.h +22 -0
  42. data/include/prism/compiler/filesystem.h +32 -0
  43. data/include/prism/compiler/flex_array.h +19 -0
  44. data/include/prism/compiler/force_inline.h +21 -0
  45. data/include/prism/compiler/format.h +25 -0
  46. data/include/prism/compiler/inline.h +17 -0
  47. data/include/prism/compiler/nodiscard.h +22 -0
  48. data/include/prism/compiler/nonnull.h +18 -0
  49. data/include/prism/compiler/unused.h +18 -0
  50. data/include/prism/constant_pool.h +81 -0
  51. data/include/prism/diagnostic.h +93 -0
  52. data/include/prism/errors_format.h +53 -0
  53. data/include/prism/excludes.h +29 -0
  54. data/include/prism/integer.h +41 -0
  55. data/include/prism/internal/allocator.h +68 -0
  56. data/include/prism/internal/allocator_debug.h +88 -0
  57. data/include/prism/internal/arena.h +108 -0
  58. data/include/prism/internal/bit.h +42 -0
  59. data/include/prism/internal/buffer.h +91 -0
  60. data/include/prism/internal/char.h +145 -0
  61. data/include/prism/internal/comments.h +53 -0
  62. data/include/prism/internal/constant_pool.h +152 -0
  63. data/include/prism/internal/diagnostic.h +389 -0
  64. data/include/prism/internal/encoding.h +242 -0
  65. data/include/prism/internal/integer.h +68 -0
  66. data/include/prism/internal/isinf.h +16 -0
  67. data/include/prism/internal/line_offset_list.h +34 -0
  68. data/include/prism/internal/list.h +62 -0
  69. data/include/prism/internal/magic_comments.h +23 -0
  70. data/include/prism/internal/memchr.h +15 -0
  71. data/include/prism/internal/node.h +38 -0
  72. data/include/prism/internal/options.h +232 -0
  73. data/include/prism/internal/parser.h +984 -0
  74. data/include/prism/internal/regexp.h +26 -0
  75. data/include/prism/internal/serialize.h +39 -0
  76. data/include/prism/internal/source.h +72 -0
  77. data/include/prism/internal/static_literals.h +100 -0
  78. data/include/prism/internal/stringy.h +35 -0
  79. data/include/prism/internal/strncasecmp.h +18 -0
  80. data/include/prism/internal/strpbrk.h +33 -0
  81. data/include/prism/internal/tokens.h +11 -0
  82. data/include/prism/json.h +32 -0
  83. data/include/prism/line_offset_list.h +61 -0
  84. data/include/prism/magic_comments.h +35 -0
  85. data/include/prism/node.h +94 -0
  86. data/include/prism/options.h +354 -0
  87. data/include/prism/parser.h +404 -0
  88. data/include/prism/prettyprint.h +31 -0
  89. data/include/prism/serialize.h +115 -0
  90. data/include/prism/source.h +155 -0
  91. data/include/prism/stream.h +28 -0
  92. data/include/prism/string_query.h +63 -0
  93. data/include/prism/stringy.h +72 -0
  94. data/include/prism/version.h +38 -0
  95. data/include/prism.h +131 -0
  96. data/lib/prism/3.3/prism.so +0 -0
  97. data/lib/prism/3.4/prism.so +0 -0
  98. data/lib/prism/4.0/prism.so +0 -0
  99. data/lib/prism/compiler.rb +815 -0
  100. data/lib/prism/desugar_compiler.rb +463 -0
  101. data/lib/prism/dispatcher.rb +2383 -0
  102. data/lib/prism/dot_visitor.rb +4848 -0
  103. data/lib/prism/dsl.rb +1369 -0
  104. data/lib/prism/ffi.rb +740 -0
  105. data/lib/prism/inspect_visitor.rb +2425 -0
  106. data/lib/prism/lex_compat.rb +953 -0
  107. data/lib/prism/mutation_compiler.rb +780 -0
  108. data/lib/prism/node.rb +33204 -0
  109. data/lib/prism/node_ext.rb +504 -0
  110. data/lib/prism/node_find.rb +215 -0
  111. data/lib/prism/parse_result/errors.rb +72 -0
  112. data/lib/prism/parse_result/newlines.rb +423 -0
  113. data/lib/prism/parse_result.rb +1221 -0
  114. data/lib/prism/pattern.rb +314 -0
  115. data/lib/prism/polyfill/append_as_bytes.rb +15 -0
  116. data/lib/prism/polyfill/byteindex.rb +13 -0
  117. data/lib/prism/polyfill/scan_byte.rb +14 -0
  118. data/lib/prism/polyfill/unpack1.rb +14 -0
  119. data/lib/prism/polyfill/warn.rb +36 -0
  120. data/lib/prism/reflection.rb +427 -0
  121. data/lib/prism/relocation.rb +567 -0
  122. data/lib/prism/serialize.rb +5308 -0
  123. data/lib/prism/string_query.rb +46 -0
  124. data/lib/prism/translation/parser/builder.rb +70 -0
  125. data/lib/prism/translation/parser/compiler.rb +2226 -0
  126. data/lib/prism/translation/parser/lexer.rb +769 -0
  127. data/lib/prism/translation/parser.rb +376 -0
  128. data/lib/prism/translation/parser_current.rb +26 -0
  129. data/lib/prism/translation/parser_versions.rb +36 -0
  130. data/lib/prism/translation/ripper/filter.rb +53 -0
  131. data/lib/prism/translation/ripper/lexer.rb +133 -0
  132. data/lib/prism/translation/ripper/sexp.rb +118 -0
  133. data/lib/prism/translation/ripper/shim.rb +7 -0
  134. data/lib/prism/translation/ripper.rb +4278 -0
  135. data/lib/prism/translation/ruby_parser.rb +1721 -0
  136. data/lib/prism/translation.rb +20 -0
  137. data/lib/prism/visitor.rb +1440 -0
  138. data/lib/prism.rb +168 -0
  139. data/prism.gemspec +234 -0
  140. data/rbi/generated/prism/compiler.rbi +489 -0
  141. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  142. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  143. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  144. data/rbi/generated/prism/dsl.rbi +751 -0
  145. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  146. data/rbi/generated/prism/lex_compat.rbi +162 -0
  147. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  148. data/rbi/generated/prism/node.rbi +13681 -0
  149. data/rbi/generated/prism/node_ext.rbi +259 -0
  150. data/rbi/generated/prism/node_find.rbi +79 -0
  151. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  152. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  153. data/rbi/generated/prism/parse_result.rbi +764 -0
  154. data/rbi/generated/prism/pattern.rbi +134 -0
  155. data/rbi/generated/prism/reflection.rbi +89 -0
  156. data/rbi/generated/prism/relocation.rbi +329 -0
  157. data/rbi/generated/prism/serialize.rbi +181 -0
  158. data/rbi/generated/prism/string_query.rbi +36 -0
  159. data/rbi/generated/prism/translation.rbi +7 -0
  160. data/rbi/generated/prism/visitor.rbi +650 -0
  161. data/rbi/generated/prism.rbi +86 -0
  162. data/rbi/prism/translation/parser.rbi +11 -0
  163. data/rbi/prism/translation/parser_versions.rbi +28 -0
  164. data/rbi/prism/translation/ripper.rbi +15 -0
  165. data/rbi/rubyvm/node_find.rbi +14 -0
  166. data/sig/generated/prism/compiler.rbs +492 -0
  167. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  168. data/sig/generated/prism/dispatcher.rbs +1292 -0
  169. data/sig/generated/prism/dot_visitor.rbs +635 -0
  170. data/sig/generated/prism/dsl.rbs +928 -0
  171. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  172. data/sig/generated/prism/lex_compat.rbs +166 -0
  173. data/sig/generated/prism/mutation_compiler.rbs +464 -0
  174. data/sig/generated/prism/node.rbs +20160 -0
  175. data/sig/generated/prism/node_ext.rbs +290 -0
  176. data/sig/generated/prism/node_find.rbs +90 -0
  177. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  178. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  179. data/sig/generated/prism/parse_result.rbs +859 -0
  180. data/sig/generated/prism/pattern.rbs +156 -0
  181. data/sig/generated/prism/reflection.rbs +101 -0
  182. data/sig/generated/prism/relocation.rbs +428 -0
  183. data/sig/generated/prism/serialize.rbs +195 -0
  184. data/sig/generated/prism/string_query.rbs +36 -0
  185. data/sig/generated/prism/translation.rbs +8 -0
  186. data/sig/generated/prism/visitor.rbs +1111 -0
  187. data/sig/generated/prism.rbs +84 -0
  188. data/sig/manifest.yaml +2 -0
  189. data/src/arena.c +117 -0
  190. data/src/buffer.c +380 -0
  191. data/src/char.c +289 -0
  192. data/src/comments.c +454 -0
  193. data/src/constant_pool.c +406 -0
  194. data/src/diagnostic.c +886 -0
  195. data/src/encoding.c +5345 -0
  196. data/src/errors_format.c +519 -0
  197. data/src/integer.c +681 -0
  198. data/src/json.c +5733 -0
  199. data/src/line_offset_list.c +100 -0
  200. data/src/list.c +24 -0
  201. data/src/memchr.c +37 -0
  202. data/src/node.c +4515 -0
  203. data/src/options.c +451 -0
  204. data/src/parser.c +349 -0
  205. data/src/prettyprint.c +9024 -0
  206. data/src/prism.c +23739 -0
  207. data/src/regexp.c +1706 -0
  208. data/src/serialize.c +2309 -0
  209. data/src/source.c +518 -0
  210. data/src/static_literals.c +685 -0
  211. data/src/string_query.c +166 -0
  212. data/src/stringy.c +91 -0
  213. data/src/strncasecmp.c +37 -0
  214. data/src/strpbrk.c +447 -0
  215. data/src/tokens.c +724 -0
  216. metadata +259 -0
@@ -0,0 +1,1221 @@
1
+ # frozen_string_literal: true
2
+ # :markup: markdown
3
+ #--
4
+ # rbs_inline: enabled
5
+
6
+ module Prism
7
+ # @rbs!
8
+ # # An internal interface for a cache that can be used to compute code
9
+ # # units from byte offsets.
10
+ # interface _CodeUnitsCache
11
+ # def []: (Integer byte_offset) -> Integer
12
+ # end
13
+
14
+ # This represents a source of Ruby code that has been parsed. It is used in
15
+ # conjunction with locations to allow them to resolve line numbers and source
16
+ # ranges.
17
+ class Source
18
+ # Create a new source object with the given source code. This method should
19
+ # be used instead of `new` and it will return either a `Source` or a
20
+ # specialized and more performant `ASCIISource` if no multibyte characters
21
+ # are present in the source code.
22
+ #
23
+ # Note that if you are calling this method manually, you will need to supply
24
+ # the start_line and offsets parameters. start_line is the line number that
25
+ # the source starts on, which is typically 1 but can be different if this
26
+ # source is a subset of a larger source or if this is an eval. offsets is an
27
+ # array of byte offsets for the start of each line in the source code, which
28
+ # can be calculated by iterating through the source code and recording the
29
+ # byte offset whenever a newline character is encountered. The first
30
+ # element is always 0 to mark the first line.
31
+ #
32
+ # The comments, comment_attachments, and comment_ranges parameters are the
33
+ # lists described by the attributes of the same names. They are passed in
34
+ # empty when the source is created by the parser and filled in afterward,
35
+ # because the comments themselves refer to the source.
36
+ #
37
+ #: (String source, Integer start_line, Array[Integer] offsets, ?Array[Comment] comments, ?Array[Comment] comment_attachments, ?Array[Integer?] comment_ranges) -> Source
38
+ def self.for(source, start_line, offsets, comments = [], comment_attachments = [], comment_ranges = [])
39
+ if source.ascii_only?
40
+ ASCIISource.new(source, start_line, offsets, comments, comment_attachments, comment_ranges)
41
+ elsif source.encoding == Encoding::BINARY
42
+ source.force_encoding(Encoding::UTF_8)
43
+
44
+ if source.valid_encoding?
45
+ new(source, start_line, offsets, comments, comment_attachments, comment_ranges)
46
+ else
47
+ # This is an extremely niche use case where the file is marked as
48
+ # binary, contains multi-byte characters, and those characters are not
49
+ # valid UTF-8. In this case we'll mark it as binary and fall back to
50
+ # treating everything as a single-byte character. This _may_ cause
51
+ # problems when asking for code units, but it appears to be the
52
+ # cleanest solution at the moment.
53
+ source.force_encoding(Encoding::BINARY)
54
+ ASCIISource.new(source, start_line, offsets, comments, comment_attachments, comment_ranges)
55
+ end
56
+ else
57
+ new(source, start_line, offsets, comments, comment_attachments, comment_ranges)
58
+ end
59
+ end
60
+
61
+ # The source code that this source object represents.
62
+ attr_reader :source #: String
63
+
64
+ # The line number where this source starts.
65
+ attr_reader :start_line #: Integer
66
+
67
+ # The list of comments that were encountered during parsing. When the source
68
+ # is parsed with the attach_comments option, these are sorted by start
69
+ # offset.
70
+ attr_reader :comments #: Array[Comment]
71
+
72
+ # The comments attached to nodes, grouped by node in order of node id. This
73
+ # is empty unless the source was parsed with the attach_comments option.
74
+ attr_reader :comment_attachments #: Array[Comment]
75
+
76
+ # Indices into comment_attachments, indexed by node id. For each node that
77
+ # owns comments, the index at which its comments begin is stored at its node
78
+ # id and the index at which they end is stored at the following node id. The
79
+ # other entries are nil. This is empty unless the source was parsed with the
80
+ # attach_comments option.
81
+ attr_reader :comment_ranges #: Array[Integer?]
82
+
83
+ # The list of newline byte offsets in the source code. When initialized from
84
+ # the C extension, this may be a packed binary string of uint32_t values
85
+ # that is lazily unpacked on first access.
86
+ #
87
+ #: () -> Array[Integer]
88
+ def offsets
89
+ offsets = @offsets
90
+ return offsets if offsets.is_a?(Array)
91
+ @offsets = offsets.unpack("L*")
92
+ end
93
+
94
+ # Create a new source object with the given source code. The offsets
95
+ # parameter can be either an Array of Integer byte offsets or a packed
96
+ # binary string of uint32_t values (from the C extension).
97
+ #
98
+ #: (String source, Integer start_line, Array[Integer] | String offsets, ?Array[Comment] comments, ?Array[Comment] comment_attachments, ?Array[Integer?] comment_ranges) -> void
99
+ def initialize(source, start_line, offsets, comments = [], comment_attachments = [], comment_ranges = [])
100
+ @source = source
101
+ @start_line = start_line
102
+ @offsets = offsets
103
+ @comments = comments
104
+ @comment_attachments = comment_attachments
105
+ @comment_ranges = comment_ranges
106
+ end
107
+
108
+ # Replace the value of start_line with the given value.
109
+ #
110
+ #: (Integer start_line) -> void
111
+ def replace_start_line(start_line)
112
+ @start_line = start_line
113
+ end
114
+
115
+ # Replace the value of offsets with the given value.
116
+ #
117
+ #: (Array[Integer] offsets) -> void
118
+ def replace_offsets(offsets)
119
+ @offsets = offsets
120
+ end
121
+
122
+ # The comments attached to the node with the given id, ordered by start
123
+ # offset. Comments are only attached when the source is parsed with the
124
+ # attach_comments option.
125
+ #
126
+ #: (Integer node_id) -> Array[Comment]
127
+ def comments_for(node_id)
128
+ start = comment_ranges[node_id]
129
+ finish = comment_ranges[node_id + 1]
130
+ return [] if start.nil? || finish.nil?
131
+
132
+ comment_attachments[start...finish] || []
133
+ end
134
+
135
+ # Returns the encoding of the source code, which is set by parameters to the
136
+ # parser or by the encoding magic comment.
137
+ #
138
+ #: () -> Encoding
139
+ def encoding
140
+ source.encoding
141
+ end
142
+
143
+ # Returns the lines of the source code as an array of strings.
144
+ #
145
+ #: () -> Array[String]
146
+ def lines
147
+ source.lines
148
+ end
149
+
150
+ # Perform a byteslice on the source code using the given byte offset and
151
+ # byte length.
152
+ #
153
+ #: (Integer byte_offset, Integer length) -> String
154
+ def slice(byte_offset, length)
155
+ source.byteslice(byte_offset, length) or raise
156
+ end
157
+
158
+ # Converts the line number and column in bytes to a byte offset.
159
+ #
160
+ #: (Integer line, Integer column) -> Integer
161
+ def byte_offset(line, column)
162
+ normal = line - @start_line
163
+ raise IndexError if normal < 0
164
+ offsets.fetch(normal) + column
165
+ rescue IndexError
166
+ raise ArgumentError, "line #{line} is out of range"
167
+ end
168
+
169
+ # Binary search through the offsets to find the line number for the given
170
+ # byte offset.
171
+ #
172
+ #: (Integer byte_offset) -> Integer
173
+ def line(byte_offset)
174
+ start_line + find_line(byte_offset)
175
+ end
176
+
177
+ # Return the byte offset of the start of the line corresponding to the given
178
+ # byte offset.
179
+ #
180
+ #: (Integer byte_offset) -> Integer
181
+ def line_start(byte_offset)
182
+ offsets[find_line(byte_offset)]
183
+ end
184
+
185
+ # Returns the byte offset of the end of the line corresponding to the given
186
+ # byte offset.
187
+ #
188
+ #: (Integer byte_offset) -> Integer
189
+ def line_end(byte_offset)
190
+ offsets[find_line(byte_offset) + 1] || source.bytesize
191
+ end
192
+
193
+ # Return the column in bytes for the given byte offset.
194
+ #
195
+ #: (Integer byte_offset) -> Integer
196
+ def column(byte_offset)
197
+ byte_offset - line_start(byte_offset)
198
+ end
199
+
200
+ # Return the character offset for the given byte offset.
201
+ #
202
+ #: (Integer byte_offset) -> Integer
203
+ def character_offset(byte_offset)
204
+ (source.byteslice(0, byte_offset) or raise).length
205
+ end
206
+
207
+ # Return the column in characters for the given byte offset.
208
+ #
209
+ #: (Integer byte_offset) -> Integer
210
+ def character_column(byte_offset)
211
+ character_offset(byte_offset) - character_offset(line_start(byte_offset))
212
+ end
213
+
214
+ # Returns the offset from the start of the file for the given byte offset
215
+ # counting in code units for the given encoding.
216
+ #
217
+ # This method is tested with UTF-8, UTF-16, and UTF-32. If there is the
218
+ # concept of code units that differs from the number of characters in other
219
+ # encodings, it is not captured here.
220
+ #
221
+ # We purposefully replace invalid and undefined characters with replacement
222
+ # characters in this conversion. This happens for two reasons. First, it's
223
+ # possible that the given byte offset will not occur on a character
224
+ # boundary. Second, it's possible that the source code will contain a
225
+ # character that has no equivalent in the given encoding.
226
+ #
227
+ #: (Integer byte_offset, Encoding encoding) -> Integer
228
+ def code_units_offset(byte_offset, encoding)
229
+ return byte_offset if encoding == Encoding::UTF_8
230
+
231
+ byteslice = (source.byteslice(0, byte_offset) or raise).encode(encoding, invalid: :replace, undef: :replace)
232
+
233
+ if encoding == Encoding::UTF_16LE || encoding == Encoding::UTF_16BE
234
+ byteslice.bytesize / 2
235
+ else
236
+ byteslice.length
237
+ end
238
+ end
239
+
240
+ # Generate a cache that targets a specific encoding for calculating code
241
+ # unit offsets.
242
+ #
243
+ #: (Encoding encoding) -> CodeUnitsCache
244
+ def code_units_cache(encoding)
245
+ CodeUnitsCache.new(source, encoding)
246
+ end
247
+
248
+ # Returns the column in code units for the given encoding for the
249
+ # given byte offset.
250
+ #
251
+ #: (Integer byte_offset, Encoding encoding) -> Integer
252
+ def code_units_column(byte_offset, encoding)
253
+ code_units_offset(byte_offset, encoding) - code_units_offset(line_start(byte_offset), encoding)
254
+ end
255
+
256
+ # Freeze this object and the objects it contains.
257
+ #
258
+ #: () -> void
259
+ def deep_freeze
260
+ source.freeze
261
+ offsets.freeze
262
+ comments.freeze
263
+ comment_attachments.freeze
264
+ comment_ranges.freeze
265
+ freeze
266
+ end
267
+
268
+ # Binary search through the offsets to find the index for the given
269
+ # byte offset.
270
+ #
271
+ #: (Integer byte_offset) -> Integer
272
+ def find_line(byte_offset) # :nodoc:
273
+ index = offsets.bsearch_index { |offset| offset > byte_offset } || offsets.length
274
+ index - 1
275
+ end
276
+ end
277
+
278
+ # A cache that can be used to quickly compute code unit offsets from byte
279
+ # offsets. It purposefully provides only a single #[] method to access the
280
+ # cache in order to minimize surface area.
281
+ #
282
+ # Note that there are some known issues here that may or may not be addressed
283
+ # in the future:
284
+ #
285
+ # * The first is that there are issues when the cache computes values that are
286
+ # not on character boundaries. This can result in subsequent computations
287
+ # being off by one or more code units.
288
+ # * The second is that this cache is currently unbounded. In theory we could
289
+ # introduce some kind of LRU cache to limit the number of entries, but this
290
+ # has not yet been implemented.
291
+ #
292
+ class CodeUnitsCache
293
+ # Counter used for UTF-8, where one code unit equals one byte.
294
+ class UTF8Counter # :nodoc:
295
+ #: (Integer byte_offset, Integer byte_length) -> Integer
296
+ def count(byte_offset, byte_length)
297
+ byte_length
298
+ end
299
+ end
300
+
301
+ class UTF16Counter # :nodoc:
302
+ # @rbs @source: String
303
+ # @rbs @encoding: Encoding
304
+
305
+ #: (String source, Encoding encoding) -> void
306
+ def initialize(source, encoding)
307
+ @source = source
308
+ @encoding = encoding
309
+ end
310
+
311
+ #: (Integer byte_offset, Integer byte_length) -> Integer
312
+ def count(byte_offset, byte_length)
313
+ (@source.byteslice(byte_offset, byte_length) or raise).encode(@encoding, invalid: :replace, undef: :replace).bytesize / 2
314
+ end
315
+ end
316
+
317
+ # Counter used for UTF-32, where one code unit equals one code point and
318
+ # matches String#length. Also used as a best-effort fallback for any other
319
+ # encoding that does not have a dedicated counter.
320
+ class UTF32Counter # :nodoc:
321
+ # @rbs @source: String
322
+ # @rbs @encoding: Encoding
323
+
324
+ #: (String source, Encoding encoding) -> void
325
+ def initialize(source, encoding)
326
+ @source = source
327
+ @encoding = encoding
328
+ end
329
+
330
+ #: (Integer byte_offset, Integer byte_length) -> Integer
331
+ def count(byte_offset, byte_length)
332
+ (@source.byteslice(byte_offset, byte_length) or raise).encode(@encoding, invalid: :replace, undef: :replace).length
333
+ end
334
+ end
335
+
336
+ private_constant :UTF8Counter, :UTF16Counter, :UTF32Counter
337
+
338
+ # @rbs @source: String
339
+ # @rbs @counter: UTF8Counter | UTF16Counter | UTF32Counter
340
+ # @rbs @cache: Hash[Integer, Integer]
341
+ # @rbs @offsets: Array[Integer]
342
+
343
+ # Initialize a new cache with the given source and encoding.
344
+ #
345
+ #: (String source, Encoding encoding) -> void
346
+ def initialize(source, encoding)
347
+ @source = source
348
+ @counter =
349
+ case encoding
350
+ when Encoding::UTF_8
351
+ UTF8Counter.new
352
+ when Encoding::UTF_16LE, Encoding::UTF_16BE
353
+ UTF16Counter.new(source, encoding)
354
+ else
355
+ UTF32Counter.new(source, encoding)
356
+ end
357
+
358
+ @cache = {} #: Hash[Integer, Integer]
359
+ @offsets = [] #: Array[Integer]
360
+ end
361
+
362
+ # Retrieve the code units offset from the given byte offset.
363
+ #
364
+ #: (Integer byte_offset) -> Integer
365
+ def [](byte_offset)
366
+ @cache[byte_offset] ||=
367
+ if (index = @offsets.bsearch_index { |offset| offset > byte_offset }).nil?
368
+ @offsets << byte_offset
369
+ @counter.count(0, byte_offset)
370
+ elsif index == 0
371
+ @offsets.unshift(byte_offset)
372
+ @counter.count(0, byte_offset)
373
+ else
374
+ @offsets.insert(index, byte_offset)
375
+ offset = @offsets[index - 1]
376
+ @cache[offset] + @counter.count(offset, byte_offset - offset)
377
+ end
378
+ end
379
+ end
380
+
381
+ # Specialized version of Prism::Source for source code that includes ASCII
382
+ # characters only. This class is used to apply performance optimizations that
383
+ # cannot be applied to sources that include multibyte characters.
384
+ #
385
+ # In the extremely rare case that a source includes multi-byte characters but
386
+ # is marked as binary because of a magic encoding comment and it cannot be
387
+ # eagerly converted to UTF-8, this class will be used as well. This is because
388
+ # at that point we will treat everything as single-byte characters.
389
+ class ASCIISource < Source
390
+ # Return the character offset for the given byte offset.
391
+ #
392
+ #: (Integer byte_offset) -> Integer
393
+ def character_offset(byte_offset)
394
+ byte_offset
395
+ end
396
+
397
+ # Return the column in characters for the given byte offset.
398
+ #
399
+ #: (Integer byte_offset) -> Integer
400
+ def character_column(byte_offset)
401
+ byte_offset - line_start(byte_offset)
402
+ end
403
+
404
+ # Returns the offset from the start of the file for the given byte offset
405
+ # counting in code units for the given encoding.
406
+ #
407
+ # This method is tested with UTF-8, UTF-16, and UTF-32. If there is the
408
+ # concept of code units that differs from the number of characters in other
409
+ # encodings, it is not captured here.
410
+ #
411
+ #: (Integer byte_offset, Encoding encoding) -> Integer
412
+ def code_units_offset(byte_offset, encoding)
413
+ byte_offset
414
+ end
415
+
416
+ # Returns a cache that is the identity function in order to maintain the
417
+ # same interface. We can do this because code units are always equivalent to
418
+ # byte offsets for ASCII-only sources.
419
+ #
420
+ #: (Encoding encoding) -> _CodeUnitsCache
421
+ def code_units_cache(encoding)
422
+ ->(byte_offset) { byte_offset }
423
+ end
424
+
425
+ # Specialized version of `code_units_column` that does not depend on
426
+ # `code_units_offset`, which is a more expensive operation. This is
427
+ # essentially the same as `Prism::Source#column`.
428
+ #
429
+ #: (Integer byte_offset, Encoding encoding) -> Integer
430
+ def code_units_column(byte_offset, encoding)
431
+ byte_offset - line_start(byte_offset)
432
+ end
433
+ end
434
+
435
+ # This represents a location in the source.
436
+ class Location
437
+ # A Source object that is used to determine more information from the given
438
+ # offset and length.
439
+ attr_reader :source #: Source
440
+ protected :source
441
+
442
+ # The byte offset from the beginning of the source where this location
443
+ # starts.
444
+ attr_reader :start_offset #: Integer
445
+
446
+ # The length of this location in bytes.
447
+ attr_reader :length #: Integer
448
+
449
+ # Create a new location object with the given source, start byte offset, and
450
+ # byte length.
451
+ #
452
+ #: (Source source, Integer start_offset, Integer length) -> void
453
+ def initialize(source, start_offset, length)
454
+ @source = source
455
+ @start_offset = start_offset
456
+ @length = length
457
+ end
458
+
459
+ # Create a new location object with the given options.
460
+ #
461
+ #: (?source: Source, ?start_offset: Integer, ?length: Integer) -> Location
462
+ def copy(source: self.source, start_offset: self.start_offset, length: self.length)
463
+ Location.new(source, start_offset, length)
464
+ end
465
+
466
+ # Returns a new location that is the result of chopping off the last byte.
467
+ #
468
+ #: () -> Location
469
+ def chop
470
+ copy(length: length == 0 ? length : length - 1)
471
+ end
472
+
473
+ # Returns a string representation of this location.
474
+ #
475
+ #: () -> String
476
+ def inspect # :nodoc:
477
+ "#<Prism::Location @start_offset=#{@start_offset} @length=#{@length} start_line=#{start_line}>"
478
+ end
479
+
480
+ # Returns all of the lines of the source code associated with this location.
481
+ #
482
+ #: () -> Array[String]
483
+ def source_lines
484
+ source.lines
485
+ end
486
+
487
+ # The source code that this location represents.
488
+ #
489
+ #: () -> String
490
+ def slice
491
+ source.slice(start_offset, length)
492
+ end
493
+
494
+ # The source code that this location represents starting from the beginning
495
+ # of the line that this location starts on to the end of the line that this
496
+ # location ends on.
497
+ #
498
+ #: () -> String
499
+ def slice_lines
500
+ line_start = source.line_start(start_offset)
501
+ line_end = source.line_end(end_offset)
502
+ source.slice(line_start, line_end - line_start)
503
+ end
504
+
505
+ # The character offset from the beginning of the source where this location
506
+ # starts.
507
+ #
508
+ #: () -> Integer
509
+ def start_character_offset
510
+ source.character_offset(start_offset)
511
+ end
512
+
513
+ # The offset from the start of the file in code units of the given encoding.
514
+ #
515
+ #: (Encoding encoding) -> Integer
516
+ def start_code_units_offset(encoding = Encoding::UTF_16LE)
517
+ source.code_units_offset(start_offset, encoding)
518
+ end
519
+
520
+ # The start offset from the start of the file in code units using the given
521
+ # cache to fetch or calculate the value.
522
+ #
523
+ #: (_CodeUnitsCache cache) -> Integer
524
+ def cached_start_code_units_offset(cache)
525
+ cache[start_offset]
526
+ end
527
+
528
+ # The byte offset from the beginning of the source where this location ends.
529
+ #
530
+ #: () -> Integer
531
+ def end_offset
532
+ start_offset + length
533
+ end
534
+
535
+ # The character offset from the beginning of the source where this location
536
+ # ends.
537
+ #
538
+ #: () -> Integer
539
+ def end_character_offset
540
+ source.character_offset(end_offset)
541
+ end
542
+
543
+ # The offset from the start of the file in code units of the given encoding.
544
+ #
545
+ #: (Encoding encoding) -> Integer
546
+ def end_code_units_offset(encoding = Encoding::UTF_16LE)
547
+ source.code_units_offset(end_offset, encoding)
548
+ end
549
+
550
+ # The end offset from the start of the file in code units using the given
551
+ # cache to fetch or calculate the value.
552
+ #
553
+ #: (_CodeUnitsCache cache) -> Integer
554
+ def cached_end_code_units_offset(cache)
555
+ cache[end_offset]
556
+ end
557
+
558
+ # The line number where this location starts.
559
+ #
560
+ #: () -> Integer
561
+ def start_line
562
+ source.line(start_offset)
563
+ end
564
+
565
+ # The content of the line where this location starts before this location.
566
+ #
567
+ #: () -> String
568
+ def start_line_slice
569
+ offset = source.line_start(start_offset)
570
+ source.slice(offset, start_offset - offset)
571
+ end
572
+
573
+ # The line number where this location ends.
574
+ #
575
+ #: () -> Integer
576
+ def end_line
577
+ source.line(end_offset)
578
+ end
579
+
580
+ # The column in bytes where this location starts from the start of
581
+ # the line.
582
+ #
583
+ #: () -> Integer
584
+ def start_column
585
+ source.column(start_offset)
586
+ end
587
+
588
+ # The column in characters where this location ends from the start of
589
+ # the line.
590
+ #
591
+ #: () -> Integer
592
+ def start_character_column
593
+ source.character_column(start_offset)
594
+ end
595
+
596
+ # The column in code units of the given encoding where this location
597
+ # starts from the start of the line.
598
+ #
599
+ #: (?Encoding encoding) -> Integer
600
+ def start_code_units_column(encoding = Encoding::UTF_16LE)
601
+ source.code_units_column(start_offset, encoding)
602
+ end
603
+
604
+ # The start column in code units using the given cache to fetch or calculate
605
+ # the value.
606
+ #
607
+ #: (_CodeUnitsCache cache) -> Integer
608
+ def cached_start_code_units_column(cache)
609
+ cache[start_offset] - cache[source.line_start(start_offset)]
610
+ end
611
+
612
+ # The column in bytes where this location ends from the start of the
613
+ # line.
614
+ #
615
+ #: () -> Integer
616
+ def end_column
617
+ source.column(end_offset)
618
+ end
619
+
620
+ # The column in characters where this location ends from the start of
621
+ # the line.
622
+ #
623
+ #: () -> Integer
624
+ def end_character_column
625
+ source.character_column(end_offset)
626
+ end
627
+
628
+ # The column in code units of the given encoding where this location
629
+ # ends from the start of the line.
630
+ #
631
+ #: (?Encoding encoding) -> Integer
632
+ def end_code_units_column(encoding = Encoding::UTF_16LE)
633
+ source.code_units_column(end_offset, encoding)
634
+ end
635
+
636
+ # The end column in code units using the given cache to fetch or calculate
637
+ # the value.
638
+ #
639
+ #: (_CodeUnitsCache cache) -> Integer
640
+ def cached_end_code_units_column(cache)
641
+ cache[end_offset] - cache[source.line_start(end_offset)]
642
+ end
643
+
644
+ # Implement the hash pattern matching interface for Location.
645
+ #
646
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
647
+ def deconstruct_keys(keys) # :nodoc:
648
+ { start_offset: start_offset, end_offset: end_offset }
649
+ end
650
+
651
+ # Implement the pretty print interface for Location.
652
+ #
653
+ #: (PP q) -> void
654
+ def pretty_print(q) # :nodoc:
655
+ q.text("(#{start_line},#{start_column})-(#{end_line},#{end_column})")
656
+ end
657
+
658
+ # Returns true if the given other location is equal to this location.
659
+ #
660
+ #: (untyped other) -> bool
661
+ def ==(other)
662
+ Location === other &&
663
+ other.start_offset == start_offset &&
664
+ other.end_offset == end_offset
665
+ end
666
+
667
+ # Returns a new location that stretches from this location to the given
668
+ # other location. Raises an error if this location is not before the other
669
+ # location or if they don't share the same source.
670
+ #
671
+ #: (Location other) -> Location
672
+ def join(other)
673
+ raise "Incompatible sources" if source != other.source
674
+ raise "Incompatible locations" if start_offset > other.start_offset
675
+
676
+ Location.new(source, start_offset, other.end_offset - start_offset)
677
+ end
678
+
679
+ # Join this location with the first occurrence of the string in the source
680
+ # that occurs after this location on the same line, and return the new
681
+ # location. This will raise an error if the string does not exist.
682
+ #
683
+ #: (String string) -> Location
684
+ def adjoin(string)
685
+ line_suffix = source.slice(end_offset, source.line_end(end_offset) - end_offset)
686
+
687
+ line_suffix_index = line_suffix.byteindex(string)
688
+ raise "Could not find #{string}" if line_suffix_index.nil?
689
+
690
+ Location.new(source, start_offset, length + line_suffix_index + string.bytesize)
691
+ end
692
+ end
693
+
694
+ # This represents a comment that was encountered during parsing. It is the
695
+ # base class for all comment types.
696
+ class Comment
697
+ # The Location of this comment in the source.
698
+ attr_reader :location #: Location
699
+
700
+ # Create a new comment object with the given location.
701
+ #
702
+ #: (Location location) -> void
703
+ def initialize(location)
704
+ @location = location
705
+ end
706
+
707
+ # Implement the hash pattern matching interface for Comment.
708
+ #
709
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
710
+ def deconstruct_keys(keys) # :nodoc:
711
+ { location: location }
712
+ end
713
+
714
+ # Returns the content of the comment by slicing it from the source code.
715
+ #
716
+ #: () -> String
717
+ def slice
718
+ location.slice
719
+ end
720
+
721
+ # Returns true if this comment happens on the same line as other code and
722
+ # false if the comment is by itself. This can only be true for inline
723
+ # comments and should be false for block comments.
724
+ #
725
+ #: () -> bool
726
+ def trailing?
727
+ raise NotImplementedError, "trailing? is not implemented for #{self.class}"
728
+ end
729
+ end
730
+
731
+ # InlineComment objects are the most common. They correspond to comments in
732
+ # the source file like this one that start with #.
733
+ class InlineComment < Comment
734
+ # Returns true if this comment happens on the same line as other code and
735
+ # false if the comment is by itself.
736
+ #
737
+ #: () -> bool
738
+ def trailing?
739
+ !location.start_line_slice.strip.empty?
740
+ rescue ArgumentError # invalid bytes, never whitespace only
741
+ false
742
+ end
743
+
744
+ # Returns a string representation of this comment.
745
+ #
746
+ #: () -> String
747
+ def inspect # :nodoc:
748
+ "#<Prism::InlineComment @location=#{location.inspect}>"
749
+ end
750
+ end
751
+
752
+ # EmbDocComment objects correspond to comments that are surrounded by =begin
753
+ # and =end.
754
+ class EmbDocComment < Comment
755
+ # Returns false. This can only be true for inline comments.
756
+ #
757
+ #: () -> bool
758
+ def trailing?
759
+ false
760
+ end
761
+
762
+ # Returns a string representation of this comment.
763
+ #
764
+ #: () -> String
765
+ def inspect # :nodoc:
766
+ "#<Prism::EmbDocComment @location=#{location.inspect}>"
767
+ end
768
+ end
769
+
770
+ # This represents a magic comment that was encountered during parsing.
771
+ class MagicComment
772
+ # A Location object representing the location of the key in the source.
773
+ attr_reader :key_loc #: Location
774
+
775
+ # A Location object representing the location of the value in the source.
776
+ attr_reader :value_loc #: Location
777
+
778
+ # Create a new magic comment object with the given key and value locations.
779
+ #
780
+ #: (Location key_loc, Location value_loc) -> void
781
+ def initialize(key_loc, value_loc)
782
+ @key_loc = key_loc
783
+ @value_loc = value_loc
784
+ end
785
+
786
+ # Returns the key of the magic comment by slicing it from the source code.
787
+ #
788
+ #: () -> String
789
+ def key
790
+ key_loc.slice
791
+ end
792
+
793
+ # Returns the value of the magic comment by slicing it from the source code.
794
+ #
795
+ #: () -> String
796
+ def value
797
+ value_loc.slice
798
+ end
799
+
800
+ # Implement the hash pattern matching interface for MagicComment.
801
+ #
802
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
803
+ def deconstruct_keys(keys) # :nodoc:
804
+ { key_loc: key_loc, value_loc: value_loc }
805
+ end
806
+
807
+ # Returns a string representation of this magic comment.
808
+ #
809
+ #: () -> String
810
+ def inspect # :nodoc:
811
+ "#<Prism::MagicComment @key=#{key.inspect} @value=#{value.inspect}>"
812
+ end
813
+ end
814
+
815
+ # This represents an error that was encountered during parsing.
816
+ class ParseError
817
+ # The type of error. This is an _internal_ symbol that is used for
818
+ # communicating with translation layers. It is not meant to be public API.
819
+ attr_reader :type #: Symbol
820
+
821
+ # The message associated with this error.
822
+ attr_reader :message #: String
823
+
824
+ # A Location object representing the location of this error in the source.
825
+ attr_reader :location #: Location
826
+
827
+ # The level of this error.
828
+ attr_reader :level #: Symbol
829
+
830
+ # Create a new error object with the given message and location.
831
+ #
832
+ #: (Symbol type, String message, Location location, Symbol level) -> void
833
+ def initialize(type, message, location, level)
834
+ @type = type
835
+ @message = message
836
+ @location = location
837
+ @level = level
838
+ end
839
+
840
+ # Implement the hash pattern matching interface for ParseError.
841
+ #
842
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
843
+ def deconstruct_keys(keys) # :nodoc:
844
+ { type: type, message: message, location: location, level: level }
845
+ end
846
+
847
+ # Returns a string representation of this error.
848
+ #
849
+ #: () -> String
850
+ def inspect # :nodoc:
851
+ "#<Prism::ParseError @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
852
+ end
853
+ end
854
+
855
+ # This represents a warning that was encountered during parsing.
856
+ class ParseWarning
857
+ # The type of warning. This is an _internal_ symbol that is used for
858
+ # communicating with translation layers. It is not meant to be public API.
859
+ attr_reader :type #: Symbol
860
+
861
+ # The message associated with this warning.
862
+ attr_reader :message #: String
863
+
864
+ # A Location object representing the location of this warning in the source.
865
+ attr_reader :location #: Location
866
+
867
+ # The level of this warning.
868
+ attr_reader :level #: Symbol
869
+
870
+ # Create a new warning object with the given message and location.
871
+ #
872
+ #: (Symbol type, String message, Location location, Symbol level) -> void
873
+ def initialize(type, message, location, level)
874
+ @type = type
875
+ @message = message
876
+ @location = location
877
+ @level = level
878
+ end
879
+
880
+ # Implement the hash pattern matching interface for ParseWarning.
881
+ #
882
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
883
+ def deconstruct_keys(keys) # :nodoc:
884
+ { type: type, message: message, location: location, level: level }
885
+ end
886
+
887
+ # Returns a string representation of this warning.
888
+ #
889
+ #: () -> String
890
+ def inspect # :nodoc:
891
+ "#<Prism::ParseWarning @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
892
+ end
893
+ end
894
+
895
+ # This represents the result of a call to Prism.parse or Prism.parse_file.
896
+ # It contains the requested structure, any comments that were encounters,
897
+ # and any errors that were encountered.
898
+ class Result
899
+ # The list of magic comments that were encountered during parsing.
900
+ attr_reader :magic_comments #: Array[MagicComment]
901
+
902
+ # An optional location that represents the location of the __END__ marker
903
+ # and the rest of the content of the file. This content is loaded into the
904
+ # DATA constant when the file being parsed is the main file being executed.
905
+ attr_reader :data_loc #: Location?
906
+
907
+ # The list of errors that were generated during parsing.
908
+ attr_reader :errors #: Array[ParseError]
909
+
910
+ # The list of warnings that were generated during parsing.
911
+ attr_reader :warnings #: Array[ParseWarning]
912
+
913
+ # A Source instance that represents the source code that was parsed.
914
+ attr_reader :source #: Source
915
+
916
+ # Create a new result object with the given values.
917
+ #
918
+ #: (Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, bool continuable, Source source) -> void
919
+ def initialize(magic_comments, data_loc, errors, warnings, continuable, source)
920
+ @magic_comments = magic_comments
921
+ @data_loc = data_loc
922
+ @errors = errors
923
+ @warnings = warnings
924
+ @continuable = continuable
925
+ @source = source
926
+ end
927
+
928
+ # The list of comments that were encountered during parsing.
929
+ #
930
+ #: () -> Array[Comment]
931
+ def comments
932
+ source.comments
933
+ end
934
+
935
+ # Implement the hash pattern matching interface for Result.
936
+ #
937
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
938
+ def deconstruct_keys(keys) # :nodoc:
939
+ { comments: comments, magic_comments: magic_comments, data_loc: data_loc, errors: errors, warnings: warnings }
940
+ end
941
+
942
+ # Returns the encoding of the source code that was parsed.
943
+ #
944
+ #: () -> Encoding
945
+ def encoding
946
+ source.encoding
947
+ end
948
+
949
+ # Returns true if there were no errors during parsing and false if there
950
+ # were.
951
+ #
952
+ #: () -> bool
953
+ def success?
954
+ errors.empty?
955
+ end
956
+
957
+ # Returns true if there were errors during parsing and false if there were
958
+ # not.
959
+ #
960
+ #: () -> bool
961
+ def failure?
962
+ !success?
963
+ end
964
+
965
+ # Returns true if the parsed source is an incomplete expression that could
966
+ # become valid with additional input. This is useful for REPL contexts (such
967
+ # as IRB) where the user may be entering a multi-line expression one line at
968
+ # a time and the implementation needs to determine whether to wait for more
969
+ # input or to evaluate what has been entered so far.
970
+ #
971
+ # Concretely, this returns true when every error present is caused by the
972
+ # parser reaching the end of the input before a construct was closed (e.g.
973
+ # an unclosed string, array, block, or keyword), and returns false when any
974
+ # error is caused by a token that makes the input structurally invalid
975
+ # regardless of what might follow (e.g. a stray `end`, `]`, or `)` with no
976
+ # matching opener).
977
+ #
978
+ # Examples:
979
+ #
980
+ # Prism.parse("1 + [").continuable? #=> true (unclosed array)
981
+ # Prism.parse("1 + ]").continuable? #=> false (stray ])
982
+ # Prism.parse("tap do").continuable? #=> true (unclosed block)
983
+ # Prism.parse("end.tap do").continuable? #=> false (stray end)
984
+ #
985
+ #: () -> bool
986
+ def continuable?
987
+ @continuable
988
+ end
989
+
990
+ # Create a code units cache for the given encoding.
991
+ #
992
+ #: (Encoding encoding) -> _CodeUnitsCache
993
+ def code_units_cache(encoding)
994
+ source.code_units_cache(encoding)
995
+ end
996
+ end
997
+
998
+ # This is a result specific to the `parse` and `parse_file` methods.
999
+ class ParseResult < Result
1000
+ autoload :Errors, "prism/parse_result/errors"
1001
+ autoload :Newlines, "prism/parse_result/newlines"
1002
+
1003
+ private_constant :Errors
1004
+ private_constant :Newlines
1005
+
1006
+ # The syntax tree that was parsed from the source code.
1007
+ attr_reader :value #: ProgramNode
1008
+
1009
+ # Create a new parse result object with the given values.
1010
+ #
1011
+ #: (ProgramNode value, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, bool continuable, Source source) -> void
1012
+ def initialize(value, magic_comments, data_loc, errors, warnings, continuable, source)
1013
+ @value = value
1014
+ super(magic_comments, data_loc, errors, warnings, continuable, source)
1015
+ end
1016
+
1017
+ # Implement the hash pattern matching interface for ParseResult.
1018
+ #
1019
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
1020
+ def deconstruct_keys(keys) # :nodoc:
1021
+ super.merge!(value: value)
1022
+ end
1023
+
1024
+ # Walk the tree and mark nodes that are on a new line, loosely emulating
1025
+ # the behavior of CRuby's `:line` tracepoint event.
1026
+ #
1027
+ #: () -> void
1028
+ def mark_newlines!
1029
+ value.accept(Newlines.new(source.offsets.size)) # steep:ignore
1030
+ end
1031
+
1032
+ # Returns a string representation of the syntax tree with the errors
1033
+ # displayed inline.
1034
+ #
1035
+ #: () -> String
1036
+ def errors_format
1037
+ Errors.new(self).format
1038
+ end
1039
+ end
1040
+
1041
+ # This is a result specific to the `lex` and `lex_file` methods.
1042
+ class LexResult < Result
1043
+ # The list of tokens that were parsed from the source code.
1044
+ attr_reader :value #: Array[Token]
1045
+
1046
+ # Create a new lex result object with the given values.
1047
+ #
1048
+ #: (Array[Token] value, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, bool continuable, Source source) -> void
1049
+ def initialize(value, magic_comments, data_loc, errors, warnings, continuable, source)
1050
+ @value = value
1051
+ super(magic_comments, data_loc, errors, warnings, continuable, source)
1052
+ end
1053
+
1054
+ # Implement the hash pattern matching interface for LexResult.
1055
+ #
1056
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
1057
+ def deconstruct_keys(keys) # :nodoc:
1058
+ super.merge!(value: value)
1059
+ end
1060
+ end
1061
+
1062
+ # This is a result specific to the `parse_lex` and `parse_lex_file` methods.
1063
+ class ParseLexResult < Result
1064
+ # A tuple of the syntax tree and the list of tokens that were parsed from
1065
+ # the source code.
1066
+ attr_reader :value #: [ProgramNode, Array[Token]]
1067
+
1068
+ # Create a new parse lex result object with the given values.
1069
+ #
1070
+ #: ([ProgramNode, Array[Token]] value, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, bool continuable, Source source) -> void
1071
+ def initialize(value, magic_comments, data_loc, errors, warnings, continuable, source)
1072
+ @value = value
1073
+ super(magic_comments, data_loc, errors, warnings, continuable, source)
1074
+ end
1075
+
1076
+ # Implement the hash pattern matching interface for ParseLexResult.
1077
+ #
1078
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
1079
+ def deconstruct_keys(keys) # :nodoc:
1080
+ super.merge!(value: value)
1081
+ end
1082
+ end
1083
+
1084
+ # This represents a token from the Ruby source.
1085
+ class Token
1086
+ # The Source object that represents the source this token came from.
1087
+ attr_reader :source #: Source
1088
+ private :source
1089
+
1090
+ # The type of token that this token is.
1091
+ attr_reader :type #: Symbol
1092
+
1093
+ # A byteslice of the source that this token represents.
1094
+ attr_reader :value #: String
1095
+
1096
+ # @rbs @location: Location | Integer
1097
+
1098
+ # Create a new token object with the given type, value, and location.
1099
+ #
1100
+ #: (Source source, Symbol type, String value, Location | Integer location, Integer state) -> void
1101
+ def initialize(source, type, value, location, state)
1102
+ @source = source
1103
+ @type = type
1104
+ @value = value
1105
+ @location = location
1106
+ @state = state
1107
+ end
1108
+
1109
+ # Implement the hash pattern matching interface for Token.
1110
+ #
1111
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
1112
+ def deconstruct_keys(keys) # :nodoc:
1113
+ { type: type, value: value, location: location }
1114
+ end
1115
+
1116
+ # A Location object representing the location of this token in the source.
1117
+ #
1118
+ #: () -> Location
1119
+ def location
1120
+ location = @location
1121
+ return location if location.is_a?(Location)
1122
+ @location = Location.new(source, location >> 32, location & 0xFFFFFFFF)
1123
+ end
1124
+
1125
+ # Implement the pretty print interface for Token.
1126
+ #
1127
+ #: (PP q) -> void
1128
+ def pretty_print(q) # :nodoc:
1129
+ q.group do
1130
+ q.text(type.to_s)
1131
+ self.location.pretty_print(q)
1132
+ q.text("(")
1133
+ q.nest(2) do
1134
+ q.breakable("")
1135
+ q.pp(value)
1136
+ end
1137
+ q.breakable("")
1138
+ q.text(")")
1139
+ end
1140
+ end
1141
+
1142
+ # Returns true if the given other token is equal to this token.
1143
+ #
1144
+ #: (untyped other) -> bool
1145
+ def ==(other)
1146
+ Token === other &&
1147
+ other.type == type &&
1148
+ other.value == value
1149
+ end
1150
+
1151
+ # Returns a string representation of this token.
1152
+ #
1153
+ #: () -> String
1154
+ def inspect # :nodoc:
1155
+ location
1156
+ super
1157
+ end
1158
+
1159
+ # Freeze this object and the objects it contains.
1160
+ #
1161
+ #: () -> void
1162
+ def deep_freeze
1163
+ value.freeze
1164
+ location.freeze
1165
+ freeze
1166
+ end
1167
+
1168
+ # For internal use only.
1169
+ #: () -> Integer
1170
+ def _ripper_state # :nodoc:
1171
+ @state
1172
+ end
1173
+
1174
+ # Backwards compatibility for Prism.lex/Prism.lex_file/Prism.parse_lex
1175
+ # when they returned a 2-element array.
1176
+
1177
+ #: (Integer index) -> Token | Integer
1178
+ def [](index) # :nodoc:
1179
+ return self if index == 0
1180
+ return @state if index == 1
1181
+ raise ArgumentError, "Invalid index #{index}"
1182
+ end
1183
+
1184
+ #: () -> Token
1185
+ def first # :nodoc:
1186
+ self
1187
+ end
1188
+ end
1189
+
1190
+ # This object is passed to the various Prism.* methods that accept the
1191
+ # `scopes` option as an element of the list. It defines both the local
1192
+ # variables visible at that scope as well as the forwarding parameters
1193
+ # available at that scope.
1194
+ class Scope
1195
+ # The list of local variables that are defined in this scope. This should be
1196
+ # defined as an array of symbols.
1197
+ attr_reader :locals #: Array[Symbol]
1198
+
1199
+ # The list of local variables that are forwarded to the next scope. This
1200
+ # should by defined as an array of symbols containing the specific values of
1201
+ # :*, :**, :&, or :"...".
1202
+ attr_reader :forwarding #: Array[Symbol]
1203
+
1204
+ # Create a new scope object with the given locals and forwarding.
1205
+ #
1206
+ #: (Array[Symbol] locals, Array[Symbol] forwarding) -> void
1207
+ def initialize(locals, forwarding)
1208
+ @locals = locals
1209
+ @forwarding = forwarding
1210
+ end
1211
+ end
1212
+
1213
+ # Create a new scope with the given locals and forwarding options that is
1214
+ # suitable for passing into one of the Prism.* methods that accepts the
1215
+ # `scopes` option.
1216
+ #
1217
+ #: (?locals: Array[Symbol], ?forwarding: Array[Symbol]) -> Scope
1218
+ def self.scope(locals: [], forwarding: [])
1219
+ Scope.new(locals, forwarding)
1220
+ end
1221
+ end