prism 2.0.0.rc1-x86_64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +7 -0
  2. data/BSDmakefile +58 -0
  3. data/CHANGELOG.md +870 -0
  4. data/CODE_OF_CONDUCT.md +76 -0
  5. data/CONTRIBUTING.md +58 -0
  6. data/LICENSE.md +7 -0
  7. data/Makefile +145 -0
  8. data/README.md +81 -0
  9. data/config.yml +4752 -0
  10. data/docs/build_system.md +119 -0
  11. data/docs/configuration.md +69 -0
  12. data/docs/cruby_compilation.md +27 -0
  13. data/docs/design.md +53 -0
  14. data/docs/encoding.md +121 -0
  15. data/docs/fuzzing.md +81 -0
  16. data/docs/heredocs.md +36 -0
  17. data/docs/javascript.md +133 -0
  18. data/docs/local_variable_depth.md +229 -0
  19. data/docs/mapping.md +117 -0
  20. data/docs/parser_translation.md +24 -0
  21. data/docs/parsing_rules.md +22 -0
  22. data/docs/releasing.md +147 -0
  23. data/docs/relocation.md +32 -0
  24. data/docs/ripper_translation.md +63 -0
  25. data/docs/ruby_api.md +45 -0
  26. data/docs/ruby_parser_translation.md +19 -0
  27. data/docs/serialization.md +230 -0
  28. data/docs/testing.md +72 -0
  29. data/ext/prism/api_node.c +7005 -0
  30. data/ext/prism/extconf.rb +126 -0
  31. data/ext/prism/extension.c +1783 -0
  32. data/ext/prism/extension.h +19 -0
  33. data/include/prism/arena.h +37 -0
  34. data/include/prism/ast.h +10371 -0
  35. data/include/prism/buffer.h +52 -0
  36. data/include/prism/comments.h +55 -0
  37. data/include/prism/compiler/accel.h +24 -0
  38. data/include/prism/compiler/align.h +36 -0
  39. data/include/prism/compiler/assume.h +25 -0
  40. data/include/prism/compiler/exported.h +24 -0
  41. data/include/prism/compiler/fallthrough.h +22 -0
  42. data/include/prism/compiler/filesystem.h +32 -0
  43. data/include/prism/compiler/flex_array.h +19 -0
  44. data/include/prism/compiler/force_inline.h +21 -0
  45. data/include/prism/compiler/format.h +25 -0
  46. data/include/prism/compiler/inline.h +17 -0
  47. data/include/prism/compiler/nodiscard.h +22 -0
  48. data/include/prism/compiler/nonnull.h +18 -0
  49. data/include/prism/compiler/unused.h +18 -0
  50. data/include/prism/constant_pool.h +81 -0
  51. data/include/prism/diagnostic.h +93 -0
  52. data/include/prism/errors_format.h +53 -0
  53. data/include/prism/excludes.h +29 -0
  54. data/include/prism/integer.h +41 -0
  55. data/include/prism/internal/allocator.h +68 -0
  56. data/include/prism/internal/allocator_debug.h +88 -0
  57. data/include/prism/internal/arena.h +108 -0
  58. data/include/prism/internal/bit.h +42 -0
  59. data/include/prism/internal/buffer.h +91 -0
  60. data/include/prism/internal/char.h +145 -0
  61. data/include/prism/internal/comments.h +53 -0
  62. data/include/prism/internal/constant_pool.h +152 -0
  63. data/include/prism/internal/diagnostic.h +389 -0
  64. data/include/prism/internal/encoding.h +242 -0
  65. data/include/prism/internal/integer.h +68 -0
  66. data/include/prism/internal/isinf.h +16 -0
  67. data/include/prism/internal/line_offset_list.h +34 -0
  68. data/include/prism/internal/list.h +62 -0
  69. data/include/prism/internal/magic_comments.h +23 -0
  70. data/include/prism/internal/memchr.h +15 -0
  71. data/include/prism/internal/node.h +38 -0
  72. data/include/prism/internal/options.h +232 -0
  73. data/include/prism/internal/parser.h +984 -0
  74. data/include/prism/internal/regexp.h +26 -0
  75. data/include/prism/internal/serialize.h +39 -0
  76. data/include/prism/internal/source.h +72 -0
  77. data/include/prism/internal/static_literals.h +100 -0
  78. data/include/prism/internal/stringy.h +35 -0
  79. data/include/prism/internal/strncasecmp.h +18 -0
  80. data/include/prism/internal/strpbrk.h +33 -0
  81. data/include/prism/internal/tokens.h +11 -0
  82. data/include/prism/json.h +32 -0
  83. data/include/prism/line_offset_list.h +61 -0
  84. data/include/prism/magic_comments.h +35 -0
  85. data/include/prism/node.h +94 -0
  86. data/include/prism/options.h +354 -0
  87. data/include/prism/parser.h +404 -0
  88. data/include/prism/prettyprint.h +31 -0
  89. data/include/prism/serialize.h +115 -0
  90. data/include/prism/source.h +155 -0
  91. data/include/prism/stream.h +28 -0
  92. data/include/prism/string_query.h +63 -0
  93. data/include/prism/stringy.h +72 -0
  94. data/include/prism/version.h +38 -0
  95. data/include/prism.h +131 -0
  96. data/lib/prism/3.3/prism.so +0 -0
  97. data/lib/prism/3.4/prism.so +0 -0
  98. data/lib/prism/4.0/prism.so +0 -0
  99. data/lib/prism/compiler.rb +815 -0
  100. data/lib/prism/desugar_compiler.rb +463 -0
  101. data/lib/prism/dispatcher.rb +2383 -0
  102. data/lib/prism/dot_visitor.rb +4848 -0
  103. data/lib/prism/dsl.rb +1369 -0
  104. data/lib/prism/ffi.rb +740 -0
  105. data/lib/prism/inspect_visitor.rb +2425 -0
  106. data/lib/prism/lex_compat.rb +953 -0
  107. data/lib/prism/mutation_compiler.rb +780 -0
  108. data/lib/prism/node.rb +33204 -0
  109. data/lib/prism/node_ext.rb +504 -0
  110. data/lib/prism/node_find.rb +215 -0
  111. data/lib/prism/parse_result/errors.rb +72 -0
  112. data/lib/prism/parse_result/newlines.rb +423 -0
  113. data/lib/prism/parse_result.rb +1221 -0
  114. data/lib/prism/pattern.rb +314 -0
  115. data/lib/prism/polyfill/append_as_bytes.rb +15 -0
  116. data/lib/prism/polyfill/byteindex.rb +13 -0
  117. data/lib/prism/polyfill/scan_byte.rb +14 -0
  118. data/lib/prism/polyfill/unpack1.rb +14 -0
  119. data/lib/prism/polyfill/warn.rb +36 -0
  120. data/lib/prism/reflection.rb +427 -0
  121. data/lib/prism/relocation.rb +567 -0
  122. data/lib/prism/serialize.rb +5308 -0
  123. data/lib/prism/string_query.rb +46 -0
  124. data/lib/prism/translation/parser/builder.rb +70 -0
  125. data/lib/prism/translation/parser/compiler.rb +2226 -0
  126. data/lib/prism/translation/parser/lexer.rb +769 -0
  127. data/lib/prism/translation/parser.rb +376 -0
  128. data/lib/prism/translation/parser_current.rb +26 -0
  129. data/lib/prism/translation/parser_versions.rb +36 -0
  130. data/lib/prism/translation/ripper/filter.rb +53 -0
  131. data/lib/prism/translation/ripper/lexer.rb +133 -0
  132. data/lib/prism/translation/ripper/sexp.rb +118 -0
  133. data/lib/prism/translation/ripper/shim.rb +7 -0
  134. data/lib/prism/translation/ripper.rb +4278 -0
  135. data/lib/prism/translation/ruby_parser.rb +1721 -0
  136. data/lib/prism/translation.rb +20 -0
  137. data/lib/prism/visitor.rb +1440 -0
  138. data/lib/prism.rb +168 -0
  139. data/prism.gemspec +234 -0
  140. data/rbi/generated/prism/compiler.rbi +489 -0
  141. data/rbi/generated/prism/desugar_compiler.rbi +306 -0
  142. data/rbi/generated/prism/dispatcher.rbi +1138 -0
  143. data/rbi/generated/prism/dot_visitor.rbi +618 -0
  144. data/rbi/generated/prism/dsl.rbi +751 -0
  145. data/rbi/generated/prism/inspect_visitor.rbi +503 -0
  146. data/rbi/generated/prism/lex_compat.rbi +162 -0
  147. data/rbi/generated/prism/mutation_compiler.rbi +464 -0
  148. data/rbi/generated/prism/node.rbi +13681 -0
  149. data/rbi/generated/prism/node_ext.rbi +259 -0
  150. data/rbi/generated/prism/node_find.rbi +79 -0
  151. data/rbi/generated/prism/parse_result/errors.rbi +21 -0
  152. data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
  153. data/rbi/generated/prism/parse_result.rbi +764 -0
  154. data/rbi/generated/prism/pattern.rbi +134 -0
  155. data/rbi/generated/prism/reflection.rbi +89 -0
  156. data/rbi/generated/prism/relocation.rbi +329 -0
  157. data/rbi/generated/prism/serialize.rbi +181 -0
  158. data/rbi/generated/prism/string_query.rbi +36 -0
  159. data/rbi/generated/prism/translation.rbi +7 -0
  160. data/rbi/generated/prism/visitor.rbi +650 -0
  161. data/rbi/generated/prism.rbi +86 -0
  162. data/rbi/prism/translation/parser.rbi +11 -0
  163. data/rbi/prism/translation/parser_versions.rbi +28 -0
  164. data/rbi/prism/translation/ripper.rbi +15 -0
  165. data/rbi/rubyvm/node_find.rbi +14 -0
  166. data/sig/generated/prism/compiler.rbs +492 -0
  167. data/sig/generated/prism/desugar_compiler.rbs +305 -0
  168. data/sig/generated/prism/dispatcher.rbs +1292 -0
  169. data/sig/generated/prism/dot_visitor.rbs +635 -0
  170. data/sig/generated/prism/dsl.rbs +928 -0
  171. data/sig/generated/prism/inspect_visitor.rbs +509 -0
  172. data/sig/generated/prism/lex_compat.rbs +166 -0
  173. data/sig/generated/prism/mutation_compiler.rbs +464 -0
  174. data/sig/generated/prism/node.rbs +20160 -0
  175. data/sig/generated/prism/node_ext.rbs +290 -0
  176. data/sig/generated/prism/node_find.rbs +90 -0
  177. data/sig/generated/prism/parse_result/errors.rbs +22 -0
  178. data/sig/generated/prism/parse_result/newlines.rbs +223 -0
  179. data/sig/generated/prism/parse_result.rbs +859 -0
  180. data/sig/generated/prism/pattern.rbs +156 -0
  181. data/sig/generated/prism/reflection.rbs +101 -0
  182. data/sig/generated/prism/relocation.rbs +428 -0
  183. data/sig/generated/prism/serialize.rbs +195 -0
  184. data/sig/generated/prism/string_query.rbs +36 -0
  185. data/sig/generated/prism/translation.rbs +8 -0
  186. data/sig/generated/prism/visitor.rbs +1111 -0
  187. data/sig/generated/prism.rbs +84 -0
  188. data/sig/manifest.yaml +2 -0
  189. data/src/arena.c +117 -0
  190. data/src/buffer.c +380 -0
  191. data/src/char.c +289 -0
  192. data/src/comments.c +454 -0
  193. data/src/constant_pool.c +406 -0
  194. data/src/diagnostic.c +886 -0
  195. data/src/encoding.c +5345 -0
  196. data/src/errors_format.c +519 -0
  197. data/src/integer.c +681 -0
  198. data/src/json.c +5733 -0
  199. data/src/line_offset_list.c +100 -0
  200. data/src/list.c +24 -0
  201. data/src/memchr.c +37 -0
  202. data/src/node.c +4515 -0
  203. data/src/options.c +451 -0
  204. data/src/parser.c +349 -0
  205. data/src/prettyprint.c +9024 -0
  206. data/src/prism.c +23739 -0
  207. data/src/regexp.c +1706 -0
  208. data/src/serialize.c +2309 -0
  209. data/src/source.c +518 -0
  210. data/src/static_literals.c +685 -0
  211. data/src/string_query.c +166 -0
  212. data/src/stringy.c +91 -0
  213. data/src/strncasecmp.c +37 -0
  214. data/src/strpbrk.c +447 -0
  215. data/src/tokens.c +724 -0
  216. metadata +259 -0
@@ -0,0 +1,953 @@
1
+ # frozen_string_literal: true
2
+ # :markup: markdown
3
+ #--
4
+ # rbs_inline: enabled
5
+
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
+
35
+ # This class is responsible for lexing the source using prism and then
36
+ # converting those tokens to be compatible with Ripper. In the vast majority
37
+ # of cases, this is a one-to-one mapping of the token type. Everything else
38
+ # generally lines up. However, there are a few cases that require special
39
+ # handling.
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
+
45
+ # A result class specialized for holding tokens produced by the lexer.
46
+ class Result < Prism::Result
47
+ # The list of tokens that were produced by the lexer.
48
+ attr_reader :value #: Array[lex_compat_token]
49
+
50
+ # Create a new lex compat result object with the given values.
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)
54
+ @value = value
55
+ super(magic_comments, data_loc, errors, warnings, continuable, source)
56
+ end
57
+
58
+ # Implement the hash pattern matching interface for Result.
59
+ #
60
+ #: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
61
+ def deconstruct_keys(keys) # :nodoc:
62
+ super.merge!(value: value)
63
+ end
64
+ end
65
+
66
+ # This is a mapping of prism token types to Ripper token types. This is a
67
+ # many-to-one mapping because we split up our token types, whereas Ripper
68
+ # tends to group them.
69
+ RIPPER = {
70
+ AMPERSAND: :on_op,
71
+ AMPERSAND_AMPERSAND: :on_op,
72
+ AMPERSAND_AMPERSAND_EQUAL: :on_op,
73
+ AMPERSAND_DOT: :on_op,
74
+ AMPERSAND_EQUAL: :on_op,
75
+ BACK_REFERENCE: :on_backref,
76
+ BACKTICK: :on_backtick,
77
+ BANG: :on_op,
78
+ BANG_EQUAL: :on_op,
79
+ BANG_TILDE: :on_op,
80
+ BRACE_LEFT: :on_lbrace,
81
+ BRACE_LEFT_ARGUMENT: :on_lbrace,
82
+ BRACE_LEFT_HASH: :on_lbrace,
83
+ BRACE_RIGHT: :on_rbrace,
84
+ BRACKET_LEFT: :on_lbracket,
85
+ BRACKET_LEFT_ARRAY: :on_lbracket,
86
+ BRACKET_LEFT_RIGHT: :on_op,
87
+ BRACKET_LEFT_RIGHT_EQUAL: :on_op,
88
+ BRACKET_RIGHT: :on_rbracket,
89
+ CARET: :on_op,
90
+ CARET_EQUAL: :on_op,
91
+ CHARACTER_LITERAL: :on_CHAR,
92
+ CLASS_VARIABLE: :on_cvar,
93
+ COLON: :on_op,
94
+ COLON_COLON: :on_op,
95
+ COMMA: :on_comma,
96
+ COMMENT: :on_comment,
97
+ CONSTANT: :on_const,
98
+ DOT: :on_period,
99
+ DOT_DOT: :on_op,
100
+ DOT_DOT_DOT: :on_op,
101
+ EMBDOC_BEGIN: :on_embdoc_beg,
102
+ EMBDOC_END: :on_embdoc_end,
103
+ EMBDOC_LINE: :on_embdoc,
104
+ EMBEXPR_BEGIN: :on_embexpr_beg,
105
+ EMBEXPR_END: :on_embexpr_end,
106
+ EMBVAR: :on_embvar,
107
+ EOF: :on_eof,
108
+ EQUAL: :on_op,
109
+ EQUAL_EQUAL: :on_op,
110
+ EQUAL_EQUAL_EQUAL: :on_op,
111
+ EQUAL_GREATER: :on_op,
112
+ EQUAL_TILDE: :on_op,
113
+ FLOAT: :on_float,
114
+ FLOAT_IMAGINARY: :on_imaginary,
115
+ FLOAT_RATIONAL: :on_rational,
116
+ FLOAT_RATIONAL_IMAGINARY: :on_imaginary,
117
+ GREATER: :on_op,
118
+ GREATER_EQUAL: :on_op,
119
+ GREATER_GREATER: :on_op,
120
+ GREATER_GREATER_EQUAL: :on_op,
121
+ GLOBAL_VARIABLE: :on_gvar,
122
+ HEREDOC_END: :on_heredoc_end,
123
+ HEREDOC_START: :on_heredoc_beg,
124
+ IDENTIFIER: :on_ident,
125
+ IGNORED_NEWLINE: :on_ignored_nl,
126
+ INTEGER: :on_int,
127
+ INTEGER_IMAGINARY: :on_imaginary,
128
+ INTEGER_RATIONAL: :on_rational,
129
+ INTEGER_RATIONAL_IMAGINARY: :on_imaginary,
130
+ INSTANCE_VARIABLE: :on_ivar,
131
+ INVALID: :INVALID,
132
+ KEYWORD___ENCODING__: :on_kw,
133
+ KEYWORD___LINE__: :on_kw,
134
+ KEYWORD___FILE__: :on_kw,
135
+ KEYWORD_ALIAS: :on_kw,
136
+ KEYWORD_AND: :on_kw,
137
+ KEYWORD_BEGIN: :on_kw,
138
+ KEYWORD_BEGIN_UPCASE: :on_kw,
139
+ KEYWORD_BREAK: :on_kw,
140
+ KEYWORD_CASE: :on_kw,
141
+ KEYWORD_CLASS: :on_kw,
142
+ KEYWORD_DEF: :on_kw,
143
+ KEYWORD_DEFINED: :on_kw,
144
+ KEYWORD_DO: :on_kw,
145
+ KEYWORD_DO_BLOCK: :on_kw,
146
+ KEYWORD_DO_LAMBDA: :on_kw,
147
+ KEYWORD_DO_LOOP: :on_kw,
148
+ KEYWORD_ELSE: :on_kw,
149
+ KEYWORD_ELSIF: :on_kw,
150
+ KEYWORD_END: :on_kw,
151
+ KEYWORD_END_UPCASE: :on_kw,
152
+ KEYWORD_ENSURE: :on_kw,
153
+ KEYWORD_FALSE: :on_kw,
154
+ KEYWORD_FOR: :on_kw,
155
+ KEYWORD_IF: :on_kw,
156
+ KEYWORD_IF_MODIFIER: :on_kw,
157
+ KEYWORD_IN: :on_kw,
158
+ KEYWORD_MODULE: :on_kw,
159
+ KEYWORD_NEXT: :on_kw,
160
+ KEYWORD_NIL: :on_kw,
161
+ KEYWORD_NOT: :on_kw,
162
+ KEYWORD_OR: :on_kw,
163
+ KEYWORD_REDO: :on_kw,
164
+ KEYWORD_RESCUE: :on_kw,
165
+ KEYWORD_RESCUE_MODIFIER: :on_kw,
166
+ KEYWORD_RETRY: :on_kw,
167
+ KEYWORD_RETURN: :on_kw,
168
+ KEYWORD_SELF: :on_kw,
169
+ KEYWORD_SUPER: :on_kw,
170
+ KEYWORD_THEN: :on_kw,
171
+ KEYWORD_TRUE: :on_kw,
172
+ KEYWORD_UNDEF: :on_kw,
173
+ KEYWORD_UNLESS: :on_kw,
174
+ KEYWORD_UNLESS_MODIFIER: :on_kw,
175
+ KEYWORD_UNTIL: :on_kw,
176
+ KEYWORD_UNTIL_MODIFIER: :on_kw,
177
+ KEYWORD_WHEN: :on_kw,
178
+ KEYWORD_WHILE: :on_kw,
179
+ KEYWORD_WHILE_MODIFIER: :on_kw,
180
+ KEYWORD_YIELD: :on_kw,
181
+ LABEL: :on_label,
182
+ LABEL_END: :on_label_end,
183
+ LAMBDA_BEGIN: :on_tlambeg,
184
+ LESS: :on_op,
185
+ LESS_EQUAL: :on_op,
186
+ LESS_EQUAL_GREATER: :on_op,
187
+ LESS_LESS: :on_op,
188
+ LESS_LESS_EQUAL: :on_op,
189
+ METHOD_NAME: :on_ident,
190
+ MINUS: :on_op,
191
+ MINUS_EQUAL: :on_op,
192
+ MINUS_GREATER: :on_tlambda,
193
+ NEWLINE: :on_nl,
194
+ NEWLINE_TERMINATOR: :on_ignored_nl,
195
+ NUMBERED_REFERENCE: :on_backref,
196
+ PARENTHESIS_LEFT: :on_lparen,
197
+ PARENTHESIS_LEFT_GROUPING: :on_lparen,
198
+ PARENTHESIS_LEFT_PARENTHESES: :on_lparen,
199
+ PARENTHESIS_RIGHT: :on_rparen,
200
+ PERCENT: :on_op,
201
+ PERCENT_EQUAL: :on_op,
202
+ PERCENT_LOWER_I: :on_qsymbols_beg,
203
+ PERCENT_LOWER_W: :on_qwords_beg,
204
+ PERCENT_LOWER_X: :on_backtick,
205
+ PERCENT_UPPER_I: :on_symbols_beg,
206
+ PERCENT_UPPER_W: :on_words_beg,
207
+ PIPE: :on_op,
208
+ PIPE_EQUAL: :on_op,
209
+ PIPE_PIPE: :on_op,
210
+ PIPE_PIPE_EQUAL: :on_op,
211
+ PLUS: :on_op,
212
+ PLUS_EQUAL: :on_op,
213
+ QUESTION_MARK: :on_op,
214
+ RATIONAL_FLOAT: :on_rational,
215
+ RATIONAL_INTEGER: :on_rational,
216
+ REGEXP_BEGIN: :on_regexp_beg,
217
+ REGEXP_END: :on_regexp_end,
218
+ SEMICOLON: :on_semicolon,
219
+ SLASH: :on_op,
220
+ SLASH_EQUAL: :on_op,
221
+ STAR: :on_op,
222
+ STAR_EQUAL: :on_op,
223
+ STAR_STAR: :on_op,
224
+ STAR_STAR_EQUAL: :on_op,
225
+ STRING_BEGIN: :on_tstring_beg,
226
+ STRING_CONTENT: :on_tstring_content,
227
+ STRING_END: :on_tstring_end,
228
+ SYMBOL_BEGIN: :on_symbeg,
229
+ TILDE: :on_op,
230
+ UAMPERSAND: :on_op,
231
+ UCOLON_COLON: :on_op,
232
+ UDOT_DOT: :on_op,
233
+ UDOT_DOT_DOT: :on_op,
234
+ UMINUS: :on_op,
235
+ UMINUS_NUM: :on_op,
236
+ UPLUS: :on_op,
237
+ USTAR: :on_op,
238
+ USTAR_STAR: :on_op,
239
+ WORDS_SEP: :on_words_sep,
240
+ XSTRING_BEGIN: :on_backtick,
241
+ "__END__": :on___end__
242
+ }.freeze
243
+
244
+ # A heredoc in this case is a list of tokens that belong to the body of the
245
+ # heredoc that should be appended onto the list of tokens when the heredoc
246
+ # closes.
247
+ module Heredoc # :nodoc:
248
+ # Heredocs that are no dash or tilde heredocs are just a list of tokens.
249
+ # We need to keep them around so that we can insert them in the correct
250
+ # order back into the token stream and set the state of the last token to
251
+ # the state that the heredoc was opened in.
252
+ class PlainHeredoc # :nodoc:
253
+ attr_reader :tokens #: Array[lex_compat_token]
254
+
255
+ #: () -> void
256
+ def initialize
257
+ @tokens = []
258
+ end
259
+
260
+ #: (lex_compat_token token) -> void
261
+ def <<(token)
262
+ tokens << token
263
+ end
264
+
265
+ #: () -> Array[lex_compat_token]
266
+ def to_a
267
+ tokens
268
+ end
269
+ end
270
+
271
+ # Dash heredocs are a little more complicated. They are a list of tokens
272
+ # that need to be split on "\\\n" to mimic Ripper's behavior. We also need
273
+ # to keep track of the state that the heredoc was opened in.
274
+ class DashHeredoc # :nodoc:
275
+ attr_reader :split #: bool
276
+ attr_reader :tokens #: Array[lex_compat_token]
277
+
278
+ #: (bool split) -> void
279
+ def initialize(split)
280
+ @split = split
281
+ @tokens = []
282
+ end
283
+
284
+ #: (lex_compat_token token) -> void
285
+ def <<(token)
286
+ tokens << token
287
+ end
288
+
289
+ #: () -> Array[lex_compat_token]
290
+ def to_a
291
+ embexpr_balance = 0
292
+
293
+ tokens.each_with_object([]) do |token, results| #$ Array[lex_compat_token]
294
+ case token[1]
295
+ when :on_embexpr_beg
296
+ embexpr_balance += 1
297
+ results << token
298
+ when :on_embexpr_end
299
+ embexpr_balance -= 1
300
+ results << token
301
+ when :on_tstring_content
302
+ if embexpr_balance == 0
303
+ lineno = token[0][0]
304
+ column = token[0][1]
305
+
306
+ if split
307
+ # Split on "\\\n" to mimic Ripper's behavior. Use a lookbehind
308
+ # to keep the delimiter in the result.
309
+ token[2].split(/(?<=[^\\]\\\n)|(?<=[^\\]\\\r\n)/).each_with_index do |value, index|
310
+ column = 0 if index > 0
311
+ results << [[lineno, column], :on_tstring_content, value, token[3]]
312
+ lineno += value.count("\n")
313
+ end
314
+ else
315
+ results << token
316
+ end
317
+ else
318
+ results << token
319
+ end
320
+ else
321
+ results << token
322
+ end
323
+ end
324
+ end
325
+ end
326
+
327
+ # Heredocs that are dedenting heredocs are a little more complicated.
328
+ # Ripper outputs on_ignored_sp tokens for the whitespace that is being
329
+ # removed from the output. prism only modifies the node itself and keeps
330
+ # the token the same. This simplifies prism, but makes comparing against
331
+ # Ripper much harder because there is a length mismatch.
332
+ #
333
+ # Fortunately, we already have to pull out the heredoc tokens in order to
334
+ # insert them into the stream in the correct order. As such, we can do
335
+ # some extra manipulation on the tokens to make them match Ripper's
336
+ # output by mirroring the dedent logic that Ripper uses.
337
+ class DedentingHeredoc # :nodoc:
338
+ TAB_WIDTH = 8
339
+
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
345
+
346
+ #: () -> void
347
+ def initialize
348
+ @tokens = []
349
+ @dedent_next = true
350
+ @dedent = nil
351
+ @embexpr_balance = 0
352
+ @ended_on_newline = false
353
+ end
354
+
355
+ # As tokens are coming in, we track the minimum amount of common leading
356
+ # whitespace on plain string content tokens. This allows us to later
357
+ # remove that amount of whitespace from the beginning of each line.
358
+ #
359
+ #: (lex_compat_token token) -> void
360
+ def <<(token)
361
+ case token[1]
362
+ when :on_embexpr_beg, :on_heredoc_beg
363
+ @embexpr_balance += 1
364
+ @dedent = 0 if @dedent_next && @ended_on_newline
365
+ when :on_embexpr_end, :on_heredoc_end
366
+ @embexpr_balance -= 1
367
+ when :on_tstring_content
368
+ if embexpr_balance == 0
369
+ line = token[2]
370
+
371
+ if dedent_next && !(line.strip.empty? && line.end_with?("\n"))
372
+ leading = line[/\A(\s*)\n?/, 1] #: String
373
+ next_dedent = 0
374
+
375
+ leading.each_char do |char|
376
+ if char == "\t"
377
+ next_dedent = next_dedent - (next_dedent % TAB_WIDTH) + TAB_WIDTH
378
+ else
379
+ next_dedent += 1
380
+ end
381
+ end
382
+
383
+ @dedent = [dedent, next_dedent].compact.min
384
+ @dedent_next = true
385
+ @ended_on_newline = line.end_with?("\n")
386
+ tokens << token
387
+ return
388
+ end
389
+ end
390
+ end
391
+
392
+ @dedent_next = token[1] == :on_tstring_content && embexpr_balance == 0
393
+ @ended_on_newline = false
394
+ tokens << token
395
+ end
396
+
397
+ #: () -> Array[lex_compat_token]
398
+ def to_a
399
+ # If every line in the heredoc is blank, we still need to split up the
400
+ # string content token into multiple tokens.
401
+ if dedent.nil?
402
+ results = [] #: Array[lex_compat_token]
403
+ embexpr_balance = 0
404
+
405
+ tokens.each do |token|
406
+ case token[1]
407
+ when :on_embexpr_beg, :on_heredoc_beg
408
+ embexpr_balance += 1
409
+ results << token
410
+ when :on_embexpr_end, :on_heredoc_end
411
+ embexpr_balance -= 1
412
+ results << token
413
+ when :on_tstring_content
414
+ if embexpr_balance == 0
415
+ lineno = token[0][0]
416
+ column = token[0][1]
417
+
418
+ token[2].split(/(?<=\n)/).each_with_index do |value, index|
419
+ column = 0 if index > 0
420
+ results << [[lineno, column], :on_tstring_content, value, token[3]]
421
+ lineno += 1
422
+ end
423
+ else
424
+ results << token
425
+ end
426
+ else
427
+ results << token
428
+ end
429
+ end
430
+
431
+ return results
432
+ end
433
+
434
+ # If the minimum common whitespace is 0, then we need to concatenate
435
+ # string nodes together that are immediately adjacent.
436
+ if dedent == 0
437
+ results = [] #: Array[lex_compat_token]
438
+ embexpr_balance = 0
439
+
440
+ index = 0
441
+ max_index = tokens.length
442
+
443
+ while index < max_index
444
+ token = tokens[index]
445
+ results << token
446
+ index += 1
447
+
448
+ case token[1]
449
+ when :on_embexpr_beg, :on_heredoc_beg
450
+ embexpr_balance += 1
451
+ when :on_embexpr_end, :on_heredoc_end
452
+ embexpr_balance -= 1
453
+ when :on_tstring_content
454
+ if embexpr_balance == 0
455
+ while index < max_index && tokens[index][1] == :on_tstring_content && !token[2].match?(/\\\r?\n\z/)
456
+ token[2] << tokens[index][2]
457
+ index += 1
458
+ end
459
+ end
460
+ end
461
+ end
462
+
463
+ return results
464
+ end
465
+
466
+ # Otherwise, we're going to run through each token in the list and
467
+ # insert on_ignored_sp tokens for the amount of dedent that we need to
468
+ # perform. We also need to remove the dedent from the beginning of
469
+ # each line of plain string content tokens.
470
+ results = [] #: Array[lex_compat_token]
471
+ dedent_next = true
472
+ embexpr_balance = 0
473
+
474
+ tokens.each do |token|
475
+ # Notice that the structure of this conditional largely matches the
476
+ # whitespace calculation we performed above. This is because
477
+ # checking if the subsequent token needs to be dedented is common to
478
+ # both the dedent calculation and the ignored_sp insertion.
479
+ case token[1]
480
+ when :on_embexpr_beg
481
+ embexpr_balance += 1
482
+ results << token
483
+ when :on_embexpr_end
484
+ embexpr_balance -= 1
485
+ results << token
486
+ when :on_tstring_content
487
+ if embexpr_balance == 0
488
+ # Here we're going to split the string on newlines, but maintain
489
+ # the newlines in the resulting array. We'll do that with a look
490
+ # behind assertion.
491
+ splits = token[2].split(/(?<=\n)/)
492
+ index = 0
493
+
494
+ while index < splits.length
495
+ line = splits[index]
496
+ lineno = token[0][0] + index
497
+ column = token[0][1]
498
+
499
+ # Blank lines do not count toward common leading whitespace
500
+ # calculation and do not need to be dedented.
501
+ if dedent_next || index > 0
502
+ column = 0
503
+ end
504
+
505
+ # If the dedent is 0 and we're not supposed to dedent the next
506
+ # line or this line doesn't start with whitespace, then we
507
+ # should concatenate the rest of the string to match ripper.
508
+ if dedent == 0 && (!dedent_next || !line.start_with?(/\s/))
509
+ unjoined = splits[index..] #: Array[String]
510
+ line = unjoined.join
511
+ index = splits.length
512
+ end
513
+
514
+ # If we are supposed to dedent this line or if this is not the
515
+ # first line of the string and this line isn't entirely blank,
516
+ # then we need to insert an on_ignored_sp token and remove the
517
+ # dedent from the beginning of the line.
518
+ if (dedent > 0) && (dedent_next || index > 0)
519
+ deleting = 0
520
+ deleted_chars = [] #: Array[String]
521
+
522
+ # Gather up all of the characters that we're going to
523
+ # delete, stopping when you hit a character that would put
524
+ # you over the dedent amount.
525
+ line.each_char.with_index do |char, i|
526
+ case char
527
+ when "\r"
528
+ if line[i + 1] == "\n"
529
+ break
530
+ end
531
+ when "\n"
532
+ break
533
+ when "\t"
534
+ deleting = deleting - (deleting % TAB_WIDTH) + TAB_WIDTH
535
+ else
536
+ deleting += 1
537
+ end
538
+
539
+ break if deleting > dedent
540
+ deleted_chars << char
541
+ end
542
+
543
+ # If we have something to delete, then delete it from the
544
+ # string and insert an on_ignored_sp token.
545
+ if deleted_chars.any?
546
+ ignored = deleted_chars.join
547
+ line.delete_prefix!(ignored)
548
+
549
+ results << [[lineno, 0], :on_ignored_sp, ignored, token[3]]
550
+ column = ignored.length
551
+ end
552
+ end
553
+
554
+ results << [[lineno, column], token[1], line, token[3]] unless line.empty?
555
+ index += 1
556
+ end
557
+ else
558
+ results << token
559
+ end
560
+ else
561
+ results << token
562
+ end
563
+
564
+ dedent_next =
565
+ ((token[1] == :on_tstring_content) || (token[1] == :on_heredoc_end)) &&
566
+ embexpr_balance == 0
567
+ end
568
+
569
+ results
570
+ end
571
+ end
572
+
573
+ # Here we will split between the two types of heredocs and return the
574
+ # object that will store their tokens.
575
+ #
576
+ #: (lex_compat_token opening) -> (PlainHeredoc | DashHeredoc | DedentingHeredoc)
577
+ def self.build(opening)
578
+ case opening[2][2]
579
+ when "~"
580
+ DedentingHeredoc.new
581
+ when "-"
582
+ DashHeredoc.new(opening[2][3] != "'")
583
+ else
584
+ PlainHeredoc.new
585
+ end
586
+ end
587
+ end
588
+
589
+ private_constant :Heredoc
590
+
591
+ # In previous versions of Ruby, Ripper wouldn't flush the bom before the
592
+ # first token, so we had to have a hack in place to account for that.
593
+ BOM_FLUSHED = RUBY_VERSION >= "3.3.0"
594
+ private_constant :BOM_FLUSHED
595
+
596
+ attr_reader :options #: Hash[Symbol, untyped]
597
+ # @rbs @source: String
598
+
599
+ #: (String source, **untyped options) -> void
600
+ def initialize(source, **options)
601
+ @source = source
602
+ @options = options
603
+ end
604
+
605
+ #: () -> Result
606
+ def result
607
+ tokens = [] #: Array[lex_compat_token]
608
+
609
+ state = :default
610
+ heredoc_stack = [[]] #: Array[Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]]
611
+
612
+ result = Prism.lex(@source, **options)
613
+ source = result.source
614
+ result_value = result.value
615
+ previous_state = nil #: Translation::Ripper::Lexer::State?
616
+ last_heredoc_end = nil #: Integer?
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
664
+
665
+ # If there's a UTF-8 byte-order mark as the start of the file, then for
666
+ # certain tokens ripper sets the first token back by 3 bytes. It also
667
+ # keeps the byte order mark in the first token's value. This is weird,
668
+ # and I don't want to mirror that in our parser. So instead, we'll match
669
+ # up the columns and values here.
670
+ if bom && lineno == 1
671
+ column -= 3
672
+
673
+ if index == 0 && column == 0 && !BOM_FLUSHED
674
+ flushed =
675
+ case prism_token.type
676
+ when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE,
677
+ :GLOBAL_VARIABLE, :NUMBERED_REFERENCE, :PERCENT_LOWER_I,
678
+ :PERCENT_LOWER_X, :PERCENT_LOWER_W, :PERCENT_UPPER_I,
679
+ :PERCENT_UPPER_W, :STRING_BEGIN
680
+ true
681
+ when :REGEXP_BEGIN, :SYMBOL_BEGIN
682
+ value.start_with?("%")
683
+ else
684
+ false
685
+ end
686
+
687
+ unless flushed
688
+ column -= 3
689
+ value.prepend(String.new("\xEF\xBB\xBF", encoding: value.encoding))
690
+ end
691
+ end
692
+ end
693
+
694
+ lex_compat_token =
695
+ case event
696
+ when :on___end__
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]
701
+ when :on_comment
702
+ [[lineno, column], event, value, lex_state]
703
+ when :on_heredoc_end
704
+ # Heredoc end tokens can be emitted in an odd order, so we don't
705
+ # want to bother comparing the state on them.
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
+ when :on_embexpr_end
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
724
+ when :on_regexp_end
725
+ # On regex end, Ripper scans and then sets end state, so the ripper
726
+ # lexed output is begin, when it should be end. prism sets lex state
727
+ # correctly to end state, but we want to be able to compare against
728
+ # Ripper's lexed state. So here, if it's a regexp end token, we
729
+ # output the state as the previous state, solely for the sake of
730
+ # comparison.
731
+ lex_state =
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]
736
+ else
737
+ previous_state
738
+ end
739
+
740
+ [[lineno, column], event, value, lex_state]
741
+ when :on_eof
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
+
771
+ tokens << [[lineno, 0], :on_nl, source.slice(start_offset, end_offset - start_offset), lex_state]
772
+ end
773
+ end
774
+ end
775
+
776
+ [[lineno, column], event, value, lex_state]
777
+ else
778
+ [[lineno, column], event, value, lex_state]
779
+ end #: lex_compat_token
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
789
+
790
+ # The order in which tokens appear in our lexer is different from the
791
+ # order that they appear in Ripper. When we hit the declaration of a
792
+ # heredoc in prism, we skip forward and lex the rest of the content of
793
+ # the heredoc before going back and lexing at the end of the heredoc
794
+ # identifier.
795
+ #
796
+ # To match up to ripper, we keep a small state variable around here to
797
+ # track whether we're in the middle of a heredoc or not. In this way we
798
+ # can shuffle around the token to match Ripper's output.
799
+ case state
800
+ when :default
801
+ # The default state is when there are no heredocs at all. In this
802
+ # state we can append the token to the list of tokens and move on.
803
+ tokens << lex_compat_token
804
+
805
+ # If we get the declaration of a heredoc, then we open a new heredoc
806
+ # and move into the heredoc_opened state.
807
+ if event == :on_heredoc_beg
808
+ state = :heredoc_opened
809
+ heredoc_stack.last << Heredoc.build(lex_compat_token)
810
+ end
811
+ when :heredoc_opened
812
+ # The heredoc_opened state is when we've seen the declaration of a
813
+ # heredoc and are now lexing the body of the heredoc. In this state we
814
+ # push tokens onto the most recently created heredoc.
815
+ heredoc_stack.last.last << lex_compat_token
816
+
817
+ case event
818
+ when :on_heredoc_beg
819
+ # If we receive a heredoc declaration while lexing the body of a
820
+ # heredoc, this means we have nested heredocs. In this case we'll
821
+ # push a new heredoc onto the stack and stay in the heredoc_opened
822
+ # state since we're now lexing the body of the new heredoc.
823
+ heredoc_stack << [Heredoc.build(lex_compat_token)]
824
+ when :on_heredoc_end
825
+ # If we receive the end of a heredoc, then we're done lexing the
826
+ # body of the heredoc. In this case we now have a completed heredoc
827
+ # but need to wait for the next newline to push it into the token
828
+ # stream.
829
+ state = :heredoc_closed
830
+ end
831
+ when :heredoc_closed
832
+ if %i[on_nl on_ignored_nl on_comment].include?(event) || ((event == :on_tstring_content) && value.end_with?("\n"))
833
+ if heredoc_stack.size > 1
834
+ flushing = heredoc_stack.pop #: Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]
835
+ heredoc_stack.last.last << lex_compat_token
836
+
837
+ flushing.each do |heredoc|
838
+ heredoc.to_a.each do |flushed_token|
839
+ heredoc_stack.last.last << flushed_token
840
+ end
841
+ end
842
+
843
+ state = :heredoc_opened
844
+ next
845
+ end
846
+ elsif event == :on_heredoc_beg
847
+ tokens << lex_compat_token
848
+ state = :heredoc_opened
849
+ heredoc_stack.last << Heredoc.build(lex_compat_token)
850
+ next
851
+ elsif heredoc_stack.size > 1
852
+ heredoc_stack[-2].last << lex_compat_token
853
+ next
854
+ end
855
+
856
+ heredoc_stack.last.each do |heredoc|
857
+ tokens.concat(heredoc.to_a)
858
+ end
859
+
860
+ heredoc_stack.last.clear
861
+ state = :default
862
+
863
+ tokens << lex_compat_token
864
+ end
865
+ end
866
+
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)
880
+
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
947
+
948
+ new_tokens
949
+ end
950
+ end
951
+
952
+ private_constant :LexCompat
953
+ end