prism 1.8.1 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -2
  3. data/Makefile +41 -12
  4. data/README.md +35 -97
  5. data/config.yml +174 -163
  6. data/docs/build_system.md +2 -2
  7. data/docs/configuration.md +5 -4
  8. data/docs/encoding.md +2 -2
  9. data/docs/fuzzing.md +4 -11
  10. data/docs/javascript.md +19 -4
  11. data/docs/mapping.md +1 -1
  12. data/docs/releasing.md +77 -5
  13. data/docs/relocation.md +2 -4
  14. data/docs/ripper_translation.md +8 -17
  15. data/docs/serialization.md +21 -24
  16. data/docs/testing.md +46 -29
  17. data/ext/prism/api_node.c +1461 -1401
  18. data/ext/prism/extconf.rb +3 -4
  19. data/ext/prism/extension.c +778 -422
  20. data/ext/prism/extension.h +3 -3
  21. data/include/prism/arena.h +37 -0
  22. data/include/prism/ast.h +2729 -612
  23. data/include/prism/buffer.h +52 -0
  24. data/include/prism/comments.h +55 -0
  25. data/include/prism/compiler/accel.h +24 -0
  26. data/include/prism/compiler/align.h +36 -0
  27. data/include/prism/compiler/assume.h +25 -0
  28. data/include/prism/compiler/exported.h +24 -0
  29. data/include/prism/compiler/fallthrough.h +22 -0
  30. data/include/prism/compiler/filesystem.h +32 -0
  31. data/include/prism/compiler/flex_array.h +19 -0
  32. data/include/prism/compiler/force_inline.h +21 -0
  33. data/include/prism/compiler/format.h +25 -0
  34. data/include/prism/compiler/inline.h +17 -0
  35. data/include/prism/compiler/nodiscard.h +22 -0
  36. data/include/prism/compiler/nonnull.h +18 -0
  37. data/include/prism/compiler/unused.h +18 -0
  38. data/include/prism/constant_pool.h +81 -0
  39. data/include/prism/diagnostic.h +38 -405
  40. data/include/prism/errors_format.h +53 -0
  41. data/include/prism/excludes.h +29 -0
  42. data/include/prism/integer.h +41 -0
  43. data/include/prism/internal/allocator.h +68 -0
  44. data/include/prism/internal/allocator_debug.h +88 -0
  45. data/include/prism/internal/arena.h +108 -0
  46. data/include/prism/internal/bit.h +42 -0
  47. data/include/prism/internal/buffer.h +91 -0
  48. data/include/prism/internal/char.h +145 -0
  49. data/include/prism/internal/comments.h +53 -0
  50. data/include/prism/internal/constant_pool.h +152 -0
  51. data/include/prism/internal/diagnostic.h +389 -0
  52. data/include/prism/{encoding.h → internal/encoding.h} +27 -68
  53. data/include/prism/internal/integer.h +68 -0
  54. data/include/prism/internal/isinf.h +16 -0
  55. data/include/prism/internal/line_offset_list.h +34 -0
  56. data/include/prism/{util/pm_list.h → internal/list.h} +11 -52
  57. data/include/prism/internal/magic_comments.h +23 -0
  58. data/include/prism/internal/memchr.h +15 -0
  59. data/include/prism/internal/node.h +38 -0
  60. data/include/prism/internal/options.h +232 -0
  61. data/include/prism/internal/parser.h +984 -0
  62. data/include/prism/internal/regexp.h +26 -0
  63. data/include/prism/internal/serialize.h +39 -0
  64. data/include/prism/internal/source.h +72 -0
  65. data/include/prism/{static_literals.h → internal/static_literals.h} +26 -47
  66. data/include/prism/internal/stringy.h +35 -0
  67. data/include/prism/{util/pm_strncasecmp.h → internal/strncasecmp.h} +3 -17
  68. data/include/prism/{util/pm_strpbrk.h → internal/strpbrk.h} +7 -20
  69. data/include/prism/internal/tokens.h +11 -0
  70. data/include/prism/json.h +32 -0
  71. data/include/prism/line_offset_list.h +61 -0
  72. data/include/prism/magic_comments.h +35 -0
  73. data/include/prism/node.h +18 -53
  74. data/include/prism/options.h +146 -280
  75. data/include/prism/parser.h +336 -868
  76. data/include/prism/prettyprint.h +6 -9
  77. data/include/prism/serialize.h +115 -0
  78. data/include/prism/source.h +155 -0
  79. data/include/prism/stream.h +28 -0
  80. data/include/prism/string_query.h +63 -0
  81. data/include/prism/stringy.h +72 -0
  82. data/include/prism/version.h +13 -4
  83. data/include/prism.h +43 -320
  84. data/lib/prism/compiler.rb +319 -305
  85. data/lib/prism/desugar_compiler.rb +105 -34
  86. data/lib/prism/dispatcher.rb +646 -473
  87. data/lib/prism/dot_visitor.rb +592 -511
  88. data/lib/prism/dsl.rb +391 -25
  89. data/lib/prism/ffi.rb +252 -90
  90. data/lib/prism/inspect_visitor.rb +357 -325
  91. data/lib/prism/lex_compat.rb +311 -228
  92. data/lib/prism/mutation_compiler.rb +313 -305
  93. data/lib/prism/node.rb +20284 -7038
  94. data/lib/prism/node_ext.rb +180 -187
  95. data/lib/prism/node_find.rb +215 -0
  96. data/lib/prism/parse_result/errors.rb +7 -1
  97. data/lib/prism/parse_result/newlines.rb +276 -8
  98. data/lib/prism/parse_result.rb +462 -139
  99. data/lib/prism/pattern.rb +65 -20
  100. data/lib/prism/reflection.rb +23 -12
  101. data/lib/prism/relocation.rb +149 -87
  102. data/lib/prism/serialize.rb +3442 -536
  103. data/lib/prism/string_query.rb +16 -1
  104. data/lib/prism/translation/parser/builder.rb +10 -2
  105. data/lib/prism/translation/parser/compiler.rb +44 -52
  106. data/lib/prism/translation/parser/lexer.rb +41 -92
  107. data/lib/prism/translation/parser.rb +1 -1
  108. data/lib/prism/translation/parser_current.rb +1 -1
  109. data/lib/prism/translation/ripper/filter.rb +53 -0
  110. data/lib/prism/translation/ripper/lexer.rb +92 -5
  111. data/lib/prism/translation/ripper/sexp.rb +2 -10
  112. data/lib/prism/translation/ripper/shim.rb +2 -0
  113. data/lib/prism/translation/ripper.rb +1005 -224
  114. data/lib/prism/translation/ruby_parser.rb +73 -316
  115. data/lib/prism/translation.rb +2 -0
  116. data/lib/prism/visitor.rb +632 -5
  117. data/lib/prism.rb +79 -22
  118. data/prism.gemspec +123 -61
  119. data/rbi/generated/prism/compiler.rbi +489 -0
  120. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  121. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  122. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  123. data/rbi/generated/prism/dsl.rbi +751 -0
  124. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  125. data/rbi/generated/prism/lex_compat.rbi +162 -0
  126. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  127. data/rbi/generated/prism/node.rbi +13681 -0
  128. data/rbi/generated/prism/node_ext.rbi +259 -0
  129. data/rbi/generated/prism/node_find.rbi +79 -0
  130. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  131. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  132. data/rbi/generated/prism/parse_result.rbi +764 -0
  133. data/rbi/generated/prism/pattern.rbi +134 -0
  134. data/rbi/generated/prism/reflection.rbi +89 -0
  135. data/rbi/generated/prism/relocation.rbi +329 -0
  136. data/rbi/generated/prism/serialize.rbi +181 -0
  137. data/rbi/generated/prism/string_query.rbi +36 -0
  138. data/rbi/generated/prism/translation.rbi +7 -0
  139. data/rbi/generated/prism/visitor.rbi +650 -0
  140. data/rbi/generated/prism.rbi +86 -0
  141. data/rbi/prism/translation/parser_versions.rbi +5 -0
  142. data/rbi/rubyvm/node_find.rbi +14 -0
  143. data/sig/generated/prism/compiler.rbs +492 -0
  144. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  145. data/sig/generated/prism/dispatcher.rbs +1292 -0
  146. data/sig/generated/prism/dot_visitor.rbs +635 -0
  147. data/sig/generated/prism/dsl.rbs +928 -0
  148. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  149. data/sig/generated/prism/lex_compat.rbs +166 -0
  150. data/sig/{prism → generated/prism}/mutation_compiler.rbs +309 -4
  151. data/sig/generated/prism/node.rbs +20160 -0
  152. data/sig/generated/prism/node_ext.rbs +290 -0
  153. data/sig/generated/prism/node_find.rbs +90 -0
  154. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  155. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  156. data/sig/generated/prism/parse_result.rbs +859 -0
  157. data/sig/generated/prism/pattern.rbs +156 -0
  158. data/sig/generated/prism/reflection.rbs +101 -0
  159. data/sig/generated/prism/relocation.rbs +428 -0
  160. data/sig/generated/prism/serialize.rbs +195 -0
  161. data/sig/generated/prism/string_query.rbs +36 -0
  162. data/sig/generated/prism/translation.rbs +8 -0
  163. data/sig/generated/prism/visitor.rbs +1111 -0
  164. data/sig/generated/prism.rbs +84 -0
  165. data/sig/manifest.yaml +2 -0
  166. data/src/arena.c +117 -0
  167. data/src/{util/pm_buffer.c → buffer.c} +44 -21
  168. data/src/{util/pm_char.c → char.c} +28 -57
  169. data/src/comments.c +454 -0
  170. data/src/constant_pool.c +406 -0
  171. data/src/diagnostic.c +93 -65
  172. data/src/encoding.c +8 -3
  173. data/src/errors_format.c +519 -0
  174. data/src/{util/pm_integer.c → integer.c} +33 -22
  175. data/src/json.c +5733 -0
  176. data/src/line_offset_list.c +100 -0
  177. data/src/list.c +24 -0
  178. data/src/{util/pm_memchr.c → memchr.c} +8 -6
  179. data/src/node.c +3504 -7674
  180. data/src/options.c +162 -49
  181. data/src/parser.c +349 -0
  182. data/src/prettyprint.c +421 -354
  183. data/src/prism.c +8035 -7019
  184. data/src/regexp.c +961 -45
  185. data/src/serialize.c +459 -424
  186. data/src/source.c +518 -0
  187. data/src/static_literals.c +111 -43
  188. data/src/string_query.c +166 -0
  189. data/src/stringy.c +91 -0
  190. data/src/{util/pm_strncasecmp.c → strncasecmp.c} +5 -4
  191. data/src/strpbrk.c +447 -0
  192. data/src/{token_type.c → tokens.c} +39 -18
  193. metadata +123 -62
  194. data/ext/prism/api_pack.c +0 -276
  195. data/include/prism/defines.h +0 -260
  196. data/include/prism/pack.h +0 -163
  197. data/include/prism/regexp.h +0 -43
  198. data/include/prism/util/pm_buffer.h +0 -236
  199. data/include/prism/util/pm_char.h +0 -204
  200. data/include/prism/util/pm_constant_pool.h +0 -218
  201. data/include/prism/util/pm_integer.h +0 -130
  202. data/include/prism/util/pm_memchr.h +0 -29
  203. data/include/prism/util/pm_newline_list.h +0 -113
  204. data/include/prism/util/pm_string.h +0 -200
  205. data/lib/prism/lex_ripper.rb +0 -64
  206. data/lib/prism/pack.rb +0 -230
  207. data/lib/prism/parse_result/comments.rb +0 -188
  208. data/rbi/prism/compiler.rbi +0 -12
  209. data/rbi/prism/dsl.rbi +0 -524
  210. data/rbi/prism/inspect_visitor.rbi +0 -12
  211. data/rbi/prism/node.rbi +0 -8747
  212. data/rbi/prism/node_ext.rbi +0 -107
  213. data/rbi/prism/parse_result.rbi +0 -404
  214. data/rbi/prism/reflection.rbi +0 -58
  215. data/rbi/prism/string_query.rbi +0 -12
  216. data/rbi/prism/visitor.rbi +0 -473
  217. data/rbi/prism.rbi +0 -66
  218. data/sig/prism/compiler.rbs +0 -9
  219. data/sig/prism/dispatcher.rbs +0 -19
  220. data/sig/prism/dot_visitor.rbs +0 -6
  221. data/sig/prism/dsl.rbs +0 -351
  222. data/sig/prism/inspect_visitor.rbs +0 -22
  223. data/sig/prism/lex_compat.rbs +0 -10
  224. data/sig/prism/node.rbs +0 -4039
  225. data/sig/prism/node_ext.rbs +0 -149
  226. data/sig/prism/pack.rbs +0 -43
  227. data/sig/prism/parse_result/comments.rbs +0 -38
  228. data/sig/prism/parse_result.rbs +0 -196
  229. data/sig/prism/pattern.rbs +0 -13
  230. data/sig/prism/reflection.rbs +0 -50
  231. data/sig/prism/relocation.rbs +0 -185
  232. data/sig/prism/serialize.rbs +0 -8
  233. data/sig/prism/string_query.rbs +0 -11
  234. data/sig/prism/visitor.rbs +0 -169
  235. data/sig/prism.rbs +0 -258
  236. data/src/pack.c +0 -509
  237. data/src/util/pm_constant_pool.c +0 -342
  238. data/src/util/pm_list.c +0 -49
  239. data/src/util/pm_newline_list.c +0 -125
  240. data/src/util/pm_string.c +0 -381
  241. data/src/util/pm_strpbrk.c +0 -206
@@ -1,28 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
  # :markup: markdown
3
-
4
- require "delegate"
3
+ #--
4
+ # rbs_inline: enabled
5
5
 
6
6
  module Prism
7
+ # @rbs!
8
+ # module Translation
9
+ # class Ripper
10
+ # EXPR_NONE: Integer
11
+ # EXPR_BEG: Integer
12
+ # EXPR_MID: Integer
13
+ # EXPR_END: Integer
14
+ # EXPR_CLASS: Integer
15
+ # EXPR_VALUE: Integer
16
+ # EXPR_ARG: Integer
17
+ # EXPR_CMDARG: Integer
18
+ # EXPR_ENDARG: Integer
19
+ # EXPR_ENDFN: Integer
20
+ #
21
+ # class Lexer < Ripper
22
+ # class State
23
+ # def self.[]: (Integer value) -> State
24
+ # end
25
+ # end
26
+ #
27
+ # class LineAndColumnCache
28
+ # def initialize: (Source source) -> void
29
+ #
30
+ # def line_and_column: (Integer byte_offset) -> [Integer, Integer]
31
+ # end
32
+ # end
33
+ # end
34
+
7
35
  # This class is responsible for lexing the source using prism and then
8
36
  # converting those tokens to be compatible with Ripper. In the vast majority
9
37
  # of cases, this is a one-to-one mapping of the token type. Everything else
10
38
  # generally lines up. However, there are a few cases that require special
11
39
  # handling.
12
40
  class LexCompat # :nodoc:
41
+ # @rbs!
42
+ # # A token produced by the Ripper lexer that Prism is replicating.
43
+ # type lex_compat_token = [[Integer, Integer], Symbol, String, untyped]
44
+
13
45
  # A result class specialized for holding tokens produced by the lexer.
14
46
  class Result < Prism::Result
15
47
  # The list of tokens that were produced by the lexer.
16
- attr_reader :value
48
+ attr_reader :value #: Array[lex_compat_token]
17
49
 
18
50
  # Create a new lex compat result object with the given values.
19
- def initialize(value, comments, magic_comments, data_loc, errors, warnings, source)
51
+ #
52
+ #: (Array[lex_compat_token] value, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, bool continuable, Source source) -> void
53
+ def initialize(value, magic_comments, data_loc, errors, warnings, continuable, source)
20
54
  @value = value
21
- super(comments, magic_comments, data_loc, errors, warnings, source)
55
+ super(magic_comments, data_loc, errors, warnings, continuable, source)
22
56
  end
23
57
 
24
58
  # Implement the hash pattern matching interface for Result.
25
- def deconstruct_keys(keys)
59
+ #
60
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
61
+ def deconstruct_keys(keys) # :nodoc:
26
62
  super.merge!(value: value)
27
63
  end
28
64
  end
@@ -42,6 +78,8 @@ module Prism
42
78
  BANG_EQUAL: :on_op,
43
79
  BANG_TILDE: :on_op,
44
80
  BRACE_LEFT: :on_lbrace,
81
+ BRACE_LEFT_ARGUMENT: :on_lbrace,
82
+ BRACE_LEFT_HASH: :on_lbrace,
45
83
  BRACE_RIGHT: :on_rbrace,
46
84
  BRACKET_LEFT: :on_lbracket,
47
85
  BRACKET_LEFT_ARRAY: :on_lbracket,
@@ -104,6 +142,8 @@ module Prism
104
142
  KEYWORD_DEF: :on_kw,
105
143
  KEYWORD_DEFINED: :on_kw,
106
144
  KEYWORD_DO: :on_kw,
145
+ KEYWORD_DO_BLOCK: :on_kw,
146
+ KEYWORD_DO_LAMBDA: :on_kw,
107
147
  KEYWORD_DO_LOOP: :on_kw,
108
148
  KEYWORD_ELSE: :on_kw,
109
149
  KEYWORD_ELSIF: :on_kw,
@@ -151,8 +191,10 @@ module Prism
151
191
  MINUS_EQUAL: :on_op,
152
192
  MINUS_GREATER: :on_tlambda,
153
193
  NEWLINE: :on_nl,
194
+ NEWLINE_TERMINATOR: :on_ignored_nl,
154
195
  NUMBERED_REFERENCE: :on_backref,
155
196
  PARENTHESIS_LEFT: :on_lparen,
197
+ PARENTHESIS_LEFT_GROUPING: :on_lparen,
156
198
  PARENTHESIS_LEFT_PARENTHESES: :on_lparen,
157
199
  PARENTHESIS_RIGHT: :on_rparen,
158
200
  PERCENT: :on_op,
@@ -195,96 +237,10 @@ module Prism
195
237
  USTAR: :on_op,
196
238
  USTAR_STAR: :on_op,
197
239
  WORDS_SEP: :on_words_sep,
240
+ XSTRING_BEGIN: :on_backtick,
198
241
  "__END__": :on___end__
199
242
  }.freeze
200
243
 
201
- # When we produce tokens, we produce the same arrays that Ripper does.
202
- # However, we add a couple of convenience methods onto them to make them a
203
- # little easier to work with. We delegate all other methods to the array.
204
- class Token < SimpleDelegator
205
- # @dynamic initialize, each, []
206
-
207
- # The location of the token in the source.
208
- def location
209
- self[0]
210
- end
211
-
212
- # The type of the token.
213
- def event
214
- self[1]
215
- end
216
-
217
- # The slice of the source that this token represents.
218
- def value
219
- self[2]
220
- end
221
-
222
- # The state of the lexer when this token was produced.
223
- def state
224
- self[3]
225
- end
226
- end
227
-
228
- # Ripper doesn't include the rest of the token in the event, so we need to
229
- # trim it down to just the content on the first line when comparing.
230
- class EndContentToken < Token
231
- def ==(other) # :nodoc:
232
- [self[0], self[1], self[2][0..self[2].index("\n")], self[3]] == other
233
- end
234
- end
235
-
236
- # Tokens where state should be ignored
237
- # used for :on_comment, :on_heredoc_end, :on_embexpr_end
238
- class IgnoreStateToken < Token
239
- def ==(other) # :nodoc:
240
- self[0...-1] == other[0...-1]
241
- end
242
- end
243
-
244
- # Ident tokens for the most part are exactly the same, except sometimes we
245
- # know an ident is a local when ripper doesn't (when they are introduced
246
- # through named captures in regular expressions). In that case we don't
247
- # compare the state.
248
- class IdentToken < Token
249
- def ==(other) # :nodoc:
250
- (self[0...-1] == other[0...-1]) && (
251
- (other[3] == Translation::Ripper::EXPR_LABEL | Translation::Ripper::EXPR_END) ||
252
- (other[3] & (Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_CMDARG) != 0)
253
- )
254
- end
255
- end
256
-
257
- # Ignored newlines can occasionally have a LABEL state attached to them, so
258
- # we compare the state differently here.
259
- class IgnoredNewlineToken < Token
260
- def ==(other) # :nodoc:
261
- return false unless self[0...-1] == other[0...-1]
262
-
263
- if self[3] == Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED
264
- other[3] & Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED != 0
265
- else
266
- self[3] == other[3]
267
- end
268
- end
269
- end
270
-
271
- # If we have an identifier that follows a method name like:
272
- #
273
- # def foo bar
274
- #
275
- # then Ripper will mark bar as END|LABEL if there is a local in a parent
276
- # scope named bar because it hasn't pushed the local table yet. We do this
277
- # more accurately, so we need to allow comparing against both END and
278
- # END|LABEL.
279
- class ParamToken < Token
280
- def ==(other) # :nodoc:
281
- (self[0...-1] == other[0...-1]) && (
282
- (other[3] == Translation::Ripper::EXPR_END) ||
283
- (other[3] == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL)
284
- )
285
- end
286
- end
287
-
288
244
  # A heredoc in this case is a list of tokens that belong to the body of the
289
245
  # heredoc that should be appended onto the list of tokens when the heredoc
290
246
  # closes.
@@ -294,16 +250,19 @@ module Prism
294
250
  # order back into the token stream and set the state of the last token to
295
251
  # the state that the heredoc was opened in.
296
252
  class PlainHeredoc # :nodoc:
297
- attr_reader :tokens
253
+ attr_reader :tokens #: Array[lex_compat_token]
298
254
 
255
+ #: () -> void
299
256
  def initialize
300
257
  @tokens = []
301
258
  end
302
259
 
260
+ #: (lex_compat_token token) -> void
303
261
  def <<(token)
304
262
  tokens << token
305
263
  end
306
264
 
265
+ #: () -> Array[lex_compat_token]
307
266
  def to_a
308
267
  tokens
309
268
  end
@@ -313,22 +272,26 @@ module Prism
313
272
  # that need to be split on "\\\n" to mimic Ripper's behavior. We also need
314
273
  # to keep track of the state that the heredoc was opened in.
315
274
  class DashHeredoc # :nodoc:
316
- attr_reader :split, :tokens
275
+ attr_reader :split #: bool
276
+ attr_reader :tokens #: Array[lex_compat_token]
317
277
 
278
+ #: (bool split) -> void
318
279
  def initialize(split)
319
280
  @split = split
320
281
  @tokens = []
321
282
  end
322
283
 
284
+ #: (lex_compat_token token) -> void
323
285
  def <<(token)
324
286
  tokens << token
325
287
  end
326
288
 
289
+ #: () -> Array[lex_compat_token]
327
290
  def to_a
328
291
  embexpr_balance = 0
329
292
 
330
- tokens.each_with_object([]) do |token, results| #$ Array[Token]
331
- case token.event
293
+ tokens.each_with_object([]) do |token, results| #$ Array[lex_compat_token]
294
+ case token[1]
332
295
  when :on_embexpr_beg
333
296
  embexpr_balance += 1
334
297
  results << token
@@ -343,9 +306,9 @@ module Prism
343
306
  if split
344
307
  # Split on "\\\n" to mimic Ripper's behavior. Use a lookbehind
345
308
  # to keep the delimiter in the result.
346
- token.value.split(/(?<=[^\\]\\\n)|(?<=[^\\]\\\r\n)/).each_with_index do |value, index|
309
+ token[2].split(/(?<=[^\\]\\\n)|(?<=[^\\]\\\r\n)/).each_with_index do |value, index|
347
310
  column = 0 if index > 0
348
- results << Token.new([[lineno, column], :on_tstring_content, value, token.state])
311
+ results << [[lineno, column], :on_tstring_content, value, token[3]]
349
312
  lineno += value.count("\n")
350
313
  end
351
314
  else
@@ -374,8 +337,13 @@ module Prism
374
337
  class DedentingHeredoc # :nodoc:
375
338
  TAB_WIDTH = 8
376
339
 
377
- attr_reader :tokens, :dedent_next, :dedent, :embexpr_balance
340
+ attr_reader :tokens #: Array[lex_compat_token]
341
+ attr_reader :dedent_next #: bool
342
+ attr_reader :dedent #: Integer?
343
+ attr_reader :embexpr_balance #: Integer
344
+ # @rbs @ended_on_newline: bool
378
345
 
346
+ #: () -> void
379
347
  def initialize
380
348
  @tokens = []
381
349
  @dedent_next = true
@@ -387,8 +355,10 @@ module Prism
387
355
  # As tokens are coming in, we track the minimum amount of common leading
388
356
  # whitespace on plain string content tokens. This allows us to later
389
357
  # remove that amount of whitespace from the beginning of each line.
358
+ #
359
+ #: (lex_compat_token token) -> void
390
360
  def <<(token)
391
- case token.event
361
+ case token[1]
392
362
  when :on_embexpr_beg, :on_heredoc_beg
393
363
  @embexpr_balance += 1
394
364
  @dedent = 0 if @dedent_next && @ended_on_newline
@@ -396,10 +366,10 @@ module Prism
396
366
  @embexpr_balance -= 1
397
367
  when :on_tstring_content
398
368
  if embexpr_balance == 0
399
- line = token.value
369
+ line = token[2]
400
370
 
401
371
  if dedent_next && !(line.strip.empty? && line.end_with?("\n"))
402
- leading = line[/\A(\s*)\n?/, 1]
372
+ leading = line[/\A(\s*)\n?/, 1] #: String
403
373
  next_dedent = 0
404
374
 
405
375
  leading.each_char do |char|
@@ -419,20 +389,21 @@ module Prism
419
389
  end
420
390
  end
421
391
 
422
- @dedent_next = token.event == :on_tstring_content && embexpr_balance == 0
392
+ @dedent_next = token[1] == :on_tstring_content && embexpr_balance == 0
423
393
  @ended_on_newline = false
424
394
  tokens << token
425
395
  end
426
396
 
397
+ #: () -> Array[lex_compat_token]
427
398
  def to_a
428
399
  # If every line in the heredoc is blank, we still need to split up the
429
400
  # string content token into multiple tokens.
430
401
  if dedent.nil?
431
- results = [] #: Array[Token]
402
+ results = [] #: Array[lex_compat_token]
432
403
  embexpr_balance = 0
433
404
 
434
405
  tokens.each do |token|
435
- case token.event
406
+ case token[1]
436
407
  when :on_embexpr_beg, :on_heredoc_beg
437
408
  embexpr_balance += 1
438
409
  results << token
@@ -444,9 +415,9 @@ module Prism
444
415
  lineno = token[0][0]
445
416
  column = token[0][1]
446
417
 
447
- token.value.split(/(?<=\n)/).each_with_index do |value, index|
418
+ token[2].split(/(?<=\n)/).each_with_index do |value, index|
448
419
  column = 0 if index > 0
449
- results << Token.new([[lineno, column], :on_tstring_content, value, token.state])
420
+ results << [[lineno, column], :on_tstring_content, value, token[3]]
450
421
  lineno += 1
451
422
  end
452
423
  else
@@ -463,7 +434,7 @@ module Prism
463
434
  # If the minimum common whitespace is 0, then we need to concatenate
464
435
  # string nodes together that are immediately adjacent.
465
436
  if dedent == 0
466
- results = [] #: Array[Token]
437
+ results = [] #: Array[lex_compat_token]
467
438
  embexpr_balance = 0
468
439
 
469
440
  index = 0
@@ -474,15 +445,15 @@ module Prism
474
445
  results << token
475
446
  index += 1
476
447
 
477
- case token.event
448
+ case token[1]
478
449
  when :on_embexpr_beg, :on_heredoc_beg
479
450
  embexpr_balance += 1
480
451
  when :on_embexpr_end, :on_heredoc_end
481
452
  embexpr_balance -= 1
482
453
  when :on_tstring_content
483
454
  if embexpr_balance == 0
484
- while index < max_index && tokens[index].event == :on_tstring_content && !token.value.match?(/\\\r?\n\z/)
485
- token.value << tokens[index].value
455
+ while index < max_index && tokens[index][1] == :on_tstring_content && !token[2].match?(/\\\r?\n\z/)
456
+ token[2] << tokens[index][2]
486
457
  index += 1
487
458
  end
488
459
  end
@@ -496,7 +467,7 @@ module Prism
496
467
  # insert on_ignored_sp tokens for the amount of dedent that we need to
497
468
  # perform. We also need to remove the dedent from the beginning of
498
469
  # each line of plain string content tokens.
499
- results = [] #: Array[Token]
470
+ results = [] #: Array[lex_compat_token]
500
471
  dedent_next = true
501
472
  embexpr_balance = 0
502
473
 
@@ -505,7 +476,7 @@ module Prism
505
476
  # whitespace calculation we performed above. This is because
506
477
  # checking if the subsequent token needs to be dedented is common to
507
478
  # both the dedent calculation and the ignored_sp insertion.
508
- case token.event
479
+ case token[1]
509
480
  when :on_embexpr_beg
510
481
  embexpr_balance += 1
511
482
  results << token
@@ -517,7 +488,7 @@ module Prism
517
488
  # Here we're going to split the string on newlines, but maintain
518
489
  # the newlines in the resulting array. We'll do that with a look
519
490
  # behind assertion.
520
- splits = token.value.split(/(?<=\n)/)
491
+ splits = token[2].split(/(?<=\n)/)
521
492
  index = 0
522
493
 
523
494
  while index < splits.length
@@ -535,7 +506,8 @@ module Prism
535
506
  # line or this line doesn't start with whitespace, then we
536
507
  # should concatenate the rest of the string to match ripper.
537
508
  if dedent == 0 && (!dedent_next || !line.start_with?(/\s/))
538
- line = splits[index..].join
509
+ unjoined = splits[index..] #: Array[String]
510
+ line = unjoined.join
539
511
  index = splits.length
540
512
  end
541
513
 
@@ -574,12 +546,12 @@ module Prism
574
546
  ignored = deleted_chars.join
575
547
  line.delete_prefix!(ignored)
576
548
 
577
- results << Token.new([[lineno, 0], :on_ignored_sp, ignored, token[3]])
549
+ results << [[lineno, 0], :on_ignored_sp, ignored, token[3]]
578
550
  column = ignored.length
579
551
  end
580
552
  end
581
553
 
582
- results << Token.new([[lineno, column], token[1], line, token[3]]) unless line.empty?
554
+ results << [[lineno, column], token[1], line, token[3]] unless line.empty?
583
555
  index += 1
584
556
  end
585
557
  else
@@ -590,7 +562,7 @@ module Prism
590
562
  end
591
563
 
592
564
  dedent_next =
593
- ((token.event == :on_tstring_content) || (token.event == :on_heredoc_end)) &&
565
+ ((token[1] == :on_tstring_content) || (token[1] == :on_heredoc_end)) &&
594
566
  embexpr_balance == 0
595
567
  end
596
568
 
@@ -600,12 +572,14 @@ module Prism
600
572
 
601
573
  # Here we will split between the two types of heredocs and return the
602
574
  # object that will store their tokens.
575
+ #
576
+ #: (lex_compat_token opening) -> (PlainHeredoc | DashHeredoc | DedentingHeredoc)
603
577
  def self.build(opening)
604
- case opening.value[2]
578
+ case opening[2][2]
605
579
  when "~"
606
580
  DedentingHeredoc.new
607
581
  when "-"
608
- DashHeredoc.new(opening.value[3] != "'")
582
+ DashHeredoc.new(opening[2][3] != "'")
609
583
  else
610
584
  PlainHeredoc.new
611
585
  end
@@ -619,29 +593,74 @@ module Prism
619
593
  BOM_FLUSHED = RUBY_VERSION >= "3.3.0"
620
594
  private_constant :BOM_FLUSHED
621
595
 
622
- attr_reader :source, :options
596
+ attr_reader :options #: Hash[Symbol, untyped]
597
+ # @rbs @source: String
623
598
 
599
+ #: (String source, **untyped options) -> void
624
600
  def initialize(source, **options)
625
601
  @source = source
626
602
  @options = options
627
603
  end
628
604
 
605
+ #: () -> Result
629
606
  def result
630
- tokens = [] #: Array[LexCompat::Token]
607
+ tokens = [] #: Array[lex_compat_token]
631
608
 
632
609
  state = :default
633
610
  heredoc_stack = [[]] #: Array[Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]]
634
611
 
635
- result = Prism.lex(source, **options)
612
+ result = Prism.lex(@source, **options)
613
+ source = result.source
636
614
  result_value = result.value
637
- previous_state = nil #: State?
615
+ previous_state = nil #: Translation::Ripper::Lexer::State?
638
616
  last_heredoc_end = nil #: Integer?
639
-
640
- bom = source.byteslice(0..2) == "\xEF\xBB\xBF"
641
-
642
- result_value.each_with_index do |(token, lex_state), index|
643
- lineno = token.location.start_line
644
- column = token.location.start_column
617
+ eof_token = nil #: Token?
618
+
619
+ # The two tokens Ripper scanned most recently, oldest last. Tokens that
620
+ # this loop does not emit are not part of Ripper's view, so they never
621
+ # become either one.
622
+ previous_token = nil #: Token?
623
+ previous_previous_token = nil #: Token?
624
+
625
+ # The lex state in effect at each embedded expression that is still open,
626
+ # innermost last, and the state at the opener of the one that closed most
627
+ # recently. Ending an embedded expression restores the state its opener
628
+ # had, which on_regexp_end below has to reproduce.
629
+ embexpr_states = [] #: Array[Integer]
630
+ last_embexpr_state = nil #: Integer?
631
+
632
+ bom = source.slice(0, 3) == "\xEF\xBB\xBF"
633
+
634
+ last_comment_token = nil #: lex_compat_token?
635
+ last_comment_end = nil #: Integer?
636
+
637
+ result_value.each_with_index do |prism_token, index|
638
+ lineno = prism_token.location.start_line
639
+ column = prism_token.location.start_column
640
+
641
+ # Ripper is a scanner, so every event it emits corresponds to source
642
+ # characters. Implicit word separators have none.
643
+ next if prism_token.type == :WORDS_SEP_IMPLICIT
644
+
645
+ event = RIPPER.fetch(prism_token.type)
646
+ value = prism_token.value
647
+ lex_state = Translation::Ripper::Lexer::State[prism_token._ripper_state]
648
+
649
+ # A comment token does not include its terminating newline, but
650
+ # ripper's comment value does, so the newline token that directly
651
+ # follows a comment is folded back into it.
652
+ if last_comment_token && last_comment_end == prism_token.location.start_offset && (event == :on_nl || event == :on_ignored_nl)
653
+ last_comment_token[2] += value
654
+ last_comment_token = nil
655
+ last_comment_end = nil
656
+
657
+ # The newline's characters are part of the comment Ripper emits, so
658
+ # it is still the token Ripper scanned most recently, and the trailing
659
+ # whitespace check at on_eof measures from its end.
660
+ previous_previous_token = previous_token
661
+ previous_token = prism_token
662
+ next
663
+ end
645
664
 
646
665
  # If there's a UTF-8 byte-order mark as the start of the file, then for
647
666
  # certain tokens ripper sets the first token back by 3 bytes. It also
@@ -653,68 +672,55 @@ module Prism
653
672
 
654
673
  if index == 0 && column == 0 && !BOM_FLUSHED
655
674
  flushed =
656
- case token.type
675
+ case prism_token.type
657
676
  when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE,
658
677
  :GLOBAL_VARIABLE, :NUMBERED_REFERENCE, :PERCENT_LOWER_I,
659
678
  :PERCENT_LOWER_X, :PERCENT_LOWER_W, :PERCENT_UPPER_I,
660
679
  :PERCENT_UPPER_W, :STRING_BEGIN
661
680
  true
662
681
  when :REGEXP_BEGIN, :SYMBOL_BEGIN
663
- token.value.start_with?("%")
682
+ value.start_with?("%")
664
683
  else
665
684
  false
666
685
  end
667
686
 
668
687
  unless flushed
669
688
  column -= 3
670
- value = token.value
671
689
  value.prepend(String.new("\xEF\xBB\xBF", encoding: value.encoding))
672
690
  end
673
691
  end
674
692
  end
675
693
 
676
- event = RIPPER.fetch(token.type)
677
- value = token.value
678
- lex_state = Translation::Ripper::Lexer::State.new(lex_state)
679
-
680
- token =
694
+ lex_compat_token =
681
695
  case event
682
696
  when :on___end__
683
- EndContentToken.new([[lineno, column], event, value, lex_state])
697
+ # Ripper doesn't include the rest of the token in the event, so we need to
698
+ # trim it down to just the content on the first line.
699
+ value = value[0..value.index("\n")] #: String
700
+ [[lineno, column], event, value, lex_state]
684
701
  when :on_comment
685
- IgnoreStateToken.new([[lineno, column], event, value, lex_state])
702
+ [[lineno, column], event, value, lex_state]
686
703
  when :on_heredoc_end
687
704
  # Heredoc end tokens can be emitted in an odd order, so we don't
688
705
  # want to bother comparing the state on them.
689
- last_heredoc_end = token.location.end_offset
690
- IgnoreStateToken.new([[lineno, column], event, value, lex_state])
691
- when :on_ident
692
- if lex_state == Translation::Ripper::EXPR_END
693
- # If we have an identifier that follows a method name like:
694
- #
695
- # def foo bar
696
- #
697
- # then Ripper will mark bar as END|LABEL if there is a local in a
698
- # parent scope named bar because it hasn't pushed the local table
699
- # yet. We do this more accurately, so we need to allow comparing
700
- # against both END and END|LABEL.
701
- ParamToken.new([[lineno, column], event, value, lex_state])
702
- elsif lex_state == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL
703
- # In the event that we're comparing identifiers, we're going to
704
- # allow a little divergence. Ripper doesn't account for local
705
- # variables introduced through named captures in regexes, and we
706
- # do, which accounts for this difference.
707
- IdentToken.new([[lineno, column], event, value, lex_state])
708
- else
709
- Token.new([[lineno, column], event, value, lex_state])
710
- end
706
+ last_heredoc_end = prism_token.location.end_offset
707
+ [[lineno, column], event, value, lex_state]
708
+ when :on_embexpr_beg
709
+ embexpr_states.push(prism_token._ripper_state)
710
+ [[lineno, column], event, value, lex_state]
711
711
  when :on_embexpr_end
712
- IgnoreStateToken.new([[lineno, column], event, value, lex_state])
713
- when :on_ignored_nl
714
- # Ignored newlines can occasionally have a LABEL state attached to
715
- # them which doesn't actually impact anything. We don't mirror that
716
- # state so we ignored it.
717
- IgnoredNewlineToken.new([[lineno, column], event, value, lex_state])
712
+ last_embexpr_state = embexpr_states.pop
713
+ [[lineno, column], event, value, lex_state]
714
+ when :on_words_sep
715
+ # Ripper emits one token each per line.
716
+ value.each_line.with_index do |line, index|
717
+ if index > 0
718
+ lineno += 1
719
+ column = 0
720
+ end
721
+ tokens << [[lineno, column], event, line, lex_state]
722
+ end
723
+ tokens.pop #: lex_compat_token
718
724
  when :on_regexp_end
719
725
  # On regex end, Ripper scans and then sets end state, so the ripper
720
726
  # lexed output is begin, when it should be end. prism sets lex state
@@ -722,62 +728,64 @@ module Prism
722
728
  # Ripper's lexed state. So here, if it's a regexp end token, we
723
729
  # output the state as the previous state, solely for the sake of
724
730
  # comparison.
725
- previous_token = result_value[index - 1][0]
726
731
  lex_state =
727
- if RIPPER.fetch(previous_token.type) == :on_embexpr_end
728
- # If the previous token is embexpr_end, then we have to do even
729
- # more processing. The end of an embedded expression sets the
730
- # state to the state that it had at the beginning of the
731
- # embedded expression. So we have to go and find that state and
732
- # set it here.
733
- counter = 1
734
- current_index = index - 1
735
-
736
- until counter == 0
737
- current_index -= 1
738
- current_event = RIPPER.fetch(result_value[current_index][0].type)
739
- counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
740
- end
741
-
742
- Translation::Ripper::Lexer::State.new(result_value[current_index][1])
732
+ if previous_token&.type == :EMBEXPR_END && last_embexpr_state
733
+ # An embedded expression that just closed restored the state its
734
+ # opener had, so that is the state Ripper reports here.
735
+ Translation::Ripper::Lexer::State[last_embexpr_state]
743
736
  else
744
737
  previous_state
745
738
  end
746
739
 
747
- Token.new([[lineno, column], event, value, lex_state])
740
+ [[lineno, column], event, value, lex_state]
748
741
  when :on_eof
749
- previous_token = result_value[index - 1][0]
750
-
751
- # If we're at the end of the file and the previous token was a
752
- # comment and there is still whitespace after the comment, then
753
- # Ripper will append a on_nl token (even though there isn't
754
- # necessarily a newline). We mirror that here.
755
- if previous_token.type == :COMMENT
756
- # If the comment is at the start of a heredoc: <<HEREDOC # comment
757
- # then the comment's end_offset is up near the heredoc_beg.
758
- # This is not the correct offset to use for figuring out if
759
- # there is trailing whitespace after the last token.
760
- # Use the greater offset of the two to determine the start of
761
- # the trailing whitespace.
762
- start_offset = [previous_token.location.end_offset, last_heredoc_end].compact.max
763
- end_offset = token.location.start_offset
764
-
765
- if start_offset < end_offset
766
- if bom
767
- start_offset += 3
768
- end_offset += 3
769
- end
742
+ eof_token = prism_token
743
+
744
+ if (previous = previous_token)
745
+ # A newline that was folded back into a comment still marks the
746
+ # comment boundary for the check below.
747
+ before = previous_previous_token
748
+ comment_boundary = previous.type == :COMMENT ||
749
+ (before && %i[NEWLINE NEWLINE_TERMINATOR IGNORED_NEWLINE].include?(previous.type) && before.type == :COMMENT && before.location.end_offset == previous.location.start_offset)
750
+
751
+ # If we're at the end of the file and the previous token was a
752
+ # comment and there is still whitespace after the comment, then
753
+ # Ripper will append a on_nl token (even though there isn't
754
+ # necessarily a newline). We mirror that here.
755
+ if comment_boundary
756
+ # If the comment is at the start of a heredoc: <<HEREDOC # comment
757
+ # then the comment's end_offset is up near the heredoc_beg.
758
+ # This is not the correct offset to use for figuring out if
759
+ # there is trailing whitespace after the last token.
760
+ # Use the greater offset of the two to determine the start of
761
+ # the trailing whitespace.
762
+ start_offset = [previous.location.end_offset, last_heredoc_end].compact.max
763
+ end_offset = prism_token.location.start_offset
764
+
765
+ if start_offset < end_offset
766
+ if bom
767
+ start_offset += 3
768
+ end_offset += 3
769
+ end
770
770
 
771
- tokens << Token.new([[lineno, 0], :on_nl, source.byteslice(start_offset...end_offset), lex_state])
771
+ tokens << [[lineno, 0], :on_nl, source.slice(start_offset, end_offset - start_offset), lex_state]
772
+ end
772
773
  end
773
774
  end
774
775
 
775
- Token.new([[lineno, column], event, value, lex_state])
776
+ [[lineno, column], event, value, lex_state]
776
777
  else
777
- Token.new([[lineno, column], event, value, lex_state])
778
- end
778
+ [[lineno, column], event, value, lex_state]
779
+ end #: lex_compat_token
779
780
 
780
781
  previous_state = lex_state
782
+ previous_previous_token = previous_token
783
+ previous_token = prism_token
784
+
785
+ if event == :on_comment
786
+ last_comment_token = lex_compat_token
787
+ last_comment_end = prism_token.location.end_offset
788
+ end
781
789
 
782
790
  # The order in which tokens appear in our lexer is different from the
783
791
  # order that they appear in Ripper. When we hit the declaration of a
@@ -792,19 +800,19 @@ module Prism
792
800
  when :default
793
801
  # The default state is when there are no heredocs at all. In this
794
802
  # state we can append the token to the list of tokens and move on.
795
- tokens << token
803
+ tokens << lex_compat_token
796
804
 
797
805
  # If we get the declaration of a heredoc, then we open a new heredoc
798
806
  # and move into the heredoc_opened state.
799
807
  if event == :on_heredoc_beg
800
808
  state = :heredoc_opened
801
- heredoc_stack.last << Heredoc.build(token)
809
+ heredoc_stack.last << Heredoc.build(lex_compat_token)
802
810
  end
803
811
  when :heredoc_opened
804
812
  # The heredoc_opened state is when we've seen the declaration of a
805
813
  # heredoc and are now lexing the body of the heredoc. In this state we
806
814
  # push tokens onto the most recently created heredoc.
807
- heredoc_stack.last.last << token
815
+ heredoc_stack.last.last << lex_compat_token
808
816
 
809
817
  case event
810
818
  when :on_heredoc_beg
@@ -812,7 +820,7 @@ module Prism
812
820
  # heredoc, this means we have nested heredocs. In this case we'll
813
821
  # push a new heredoc onto the stack and stay in the heredoc_opened
814
822
  # state since we're now lexing the body of the new heredoc.
815
- heredoc_stack << [Heredoc.build(token)]
823
+ heredoc_stack << [Heredoc.build(lex_compat_token)]
816
824
  when :on_heredoc_end
817
825
  # If we receive the end of a heredoc, then we're done lexing the
818
826
  # body of the heredoc. In this case we now have a completed heredoc
@@ -821,10 +829,10 @@ module Prism
821
829
  state = :heredoc_closed
822
830
  end
823
831
  when :heredoc_closed
824
- if %i[on_nl on_ignored_nl on_comment].include?(event) || (event == :on_tstring_content && value.end_with?("\n"))
832
+ if %i[on_nl on_ignored_nl on_comment].include?(event) || ((event == :on_tstring_content) && value.end_with?("\n"))
825
833
  if heredoc_stack.size > 1
826
- flushing = heredoc_stack.pop
827
- heredoc_stack.last.last << token
834
+ flushing = heredoc_stack.pop #: Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]
835
+ heredoc_stack.last.last << lex_compat_token
828
836
 
829
837
  flushing.each do |heredoc|
830
838
  heredoc.to_a.each do |flushed_token|
@@ -836,12 +844,12 @@ module Prism
836
844
  next
837
845
  end
838
846
  elsif event == :on_heredoc_beg
839
- tokens << token
847
+ tokens << lex_compat_token
840
848
  state = :heredoc_opened
841
- heredoc_stack.last << Heredoc.build(token)
849
+ heredoc_stack.last << Heredoc.build(lex_compat_token)
842
850
  next
843
851
  elsif heredoc_stack.size > 1
844
- heredoc_stack[-2].last << token
852
+ heredoc_stack[-2].last << lex_compat_token
845
853
  next
846
854
  end
847
855
 
@@ -852,17 +860,92 @@ module Prism
852
860
  heredoc_stack.last.clear
853
861
  state = :default
854
862
 
855
- tokens << token
863
+ tokens << lex_compat_token
856
864
  end
857
865
  end
858
866
 
859
- # Drop the EOF token from the list
860
- tokens = tokens[0...-1]
867
+ # Drop the EOF token from the list. The EOF token may not be
868
+ # present if the source was syntax invalid
869
+ if tokens.dig(-1, 1) == :on_eof
870
+ tokens = tokens[0...-1] #: Array[lex_compat_token]
871
+ end
872
+
873
+ # We sort by location because Ripper.lex sorts.
874
+ tokens.sort_by! do |token|
875
+ line, column = token[0]
876
+ source.byte_offset(line, column)
877
+ end
878
+
879
+ tokens = post_process_tokens(tokens, source, result.data_loc, bom, eof_token)
861
880
 
862
- # We sort by location to compare against Ripper's output
863
- tokens.sort_by!(&:location)
881
+ Result.new(tokens, result.magic_comments, result.data_loc, result.errors, result.warnings, result.continuable?, source)
882
+ end
883
+
884
+ private
885
+
886
+ #: (Array[lex_compat_token] tokens, Source source, Location? data_loc, bool bom, Token? eof_token) -> Array[lex_compat_token]
887
+ def post_process_tokens(tokens, source, data_loc, bom, eof_token)
888
+ new_tokens = [] #: Array[lex_compat_token]
889
+
890
+ prev_token_state = Translation::Ripper::Lexer::State[Translation::Ripper::EXPR_BEG]
891
+ prev_token_end = bom ? 3 : 0
892
+
893
+ cache = Translation::Ripper::LineAndColumnCache.new(source)
894
+
895
+ tokens.each do |token|
896
+ # Skip missing heredoc ends.
897
+ next if token[1] == :on_heredoc_end && token[2] == ""
898
+
899
+ # Add :on_sp tokens.
900
+ line, column = token[0]
901
+ start_offset = source.byte_offset(line, column)
902
+
903
+ # Ripper reports columns on line 1 without counting the BOM, so we
904
+ # adjust to get the real offset
905
+ start_offset += 3 if line == 1 && bom
906
+
907
+ if start_offset > prev_token_end
908
+ sp_value = source.slice(prev_token_end, start_offset - prev_token_end)
909
+ sp_line, sp_column = cache.line_and_column(prev_token_end)
910
+ # Ripper reports columns on line 1 without counting the BOM
911
+ sp_column -= 3 if sp_line == 1 && bom
912
+ continuation_index = sp_value.byteindex("\\")
913
+
914
+ # ripper emits up to three :on_sp tokens when line continuations are used
915
+ if continuation_index
916
+ next_whitespace_index = continuation_index + 1
917
+ next_whitespace_index += 1 if sp_value.byteslice(next_whitespace_index) == "\r"
918
+ next_whitespace_index += 1
919
+ first_whitespace = sp_value[0...continuation_index] #: String
920
+ continuation = sp_value[continuation_index...next_whitespace_index] #: String
921
+ second_whitespace = sp_value[next_whitespace_index..] || ""
922
+
923
+ new_tokens << [[sp_line, sp_column], :on_sp, first_whitespace, prev_token_state] unless first_whitespace.empty?
924
+ new_tokens << [[sp_line, sp_column + continuation_index], :on_sp, continuation, prev_token_state]
925
+ new_tokens << [[sp_line + 1, 0], :on_sp, second_whitespace, prev_token_state] unless second_whitespace.empty?
926
+ else
927
+ new_tokens << [[sp_line, sp_column], :on_sp, sp_value, prev_token_state]
928
+ end
929
+ end
930
+
931
+ new_tokens << token
932
+ prev_token_state = token[3]
933
+ prev_token_end = start_offset + token[2].bytesize
934
+ end
935
+
936
+ if !data_loc && eof_token # no trailing :on_sp with __END__ as it is always preceded by :on_nl
937
+ end_offset = eof_token.location.end_offset
938
+ if prev_token_end < end_offset
939
+ new_tokens << [
940
+ [source.line(prev_token_end), source.column(prev_token_end)],
941
+ :on_sp,
942
+ source.slice(prev_token_end, end_offset - prev_token_end),
943
+ prev_token_state
944
+ ]
945
+ end
946
+ end
864
947
 
865
- Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, Source.for(source))
948
+ new_tokens
866
949
  end
867
950
  end
868
951