prism 2.0.0-arm-linux-musl

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