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,769 @@
1
+ # frozen_string_literal: true
2
+ # :markup: markdown
3
+
4
+ require "strscan"
5
+ require_relative "../../polyfill/append_as_bytes"
6
+ require_relative "../../polyfill/scan_byte"
7
+
8
+ module Prism
9
+ module Translation
10
+ class Parser
11
+ # Accepts a list of prism tokens and converts them into the expected
12
+ # format for the parser gem.
13
+ class Lexer # :nodoc:
14
+ # These tokens are always skipped
15
+ TYPES_ALWAYS_SKIP = Set.new(%i[IGNORED_NEWLINE __END__ EOF])
16
+ private_constant :TYPES_ALWAYS_SKIP
17
+
18
+ # The direct translating of types between the two lexers.
19
+ TYPES = {
20
+ # These tokens should never appear in the output of the lexer.
21
+ EMBDOC_END: nil,
22
+ EMBDOC_LINE: nil,
23
+
24
+ # These tokens have more or less direct mappings.
25
+ AMPERSAND: :tAMPER2,
26
+ AMPERSAND_AMPERSAND: :tANDOP,
27
+ AMPERSAND_AMPERSAND_EQUAL: :tOP_ASGN,
28
+ AMPERSAND_DOT: :tANDDOT,
29
+ AMPERSAND_EQUAL: :tOP_ASGN,
30
+ BACK_REFERENCE: :tBACK_REF,
31
+ BACKTICK: :tBACK_REF2,
32
+ BANG: :tBANG,
33
+ BANG_EQUAL: :tNEQ,
34
+ BANG_TILDE: :tNMATCH,
35
+ BRACE_LEFT: :tLCURLY,
36
+ BRACE_LEFT_ARGUMENT: :tLBRACE_ARG,
37
+ BRACE_LEFT_HASH: :tLBRACE,
38
+ BRACE_RIGHT: :tRCURLY,
39
+ BRACKET_LEFT: :tLBRACK2,
40
+ BRACKET_LEFT_ARRAY: :tLBRACK,
41
+ BRACKET_LEFT_RIGHT: :tAREF,
42
+ BRACKET_LEFT_RIGHT_EQUAL: :tASET,
43
+ BRACKET_RIGHT: :tRBRACK,
44
+ CARET: :tCARET,
45
+ CARET_EQUAL: :tOP_ASGN,
46
+ CHARACTER_LITERAL: :tCHARACTER,
47
+ CLASS_VARIABLE: :tCVAR,
48
+ COLON: :tCOLON,
49
+ COLON_COLON: :tCOLON2,
50
+ COMMA: :tCOMMA,
51
+ COMMENT: :tCOMMENT,
52
+ CONSTANT: :tCONSTANT,
53
+ DOT: :tDOT,
54
+ DOT_DOT: :tDOT2,
55
+ DOT_DOT_DOT: :tDOT3,
56
+ EMBDOC_BEGIN: :tCOMMENT,
57
+ EMBEXPR_BEGIN: :tSTRING_DBEG,
58
+ EMBEXPR_END: :tSTRING_DEND,
59
+ EMBVAR: :tSTRING_DVAR,
60
+ EQUAL: :tEQL,
61
+ EQUAL_EQUAL: :tEQ,
62
+ EQUAL_EQUAL_EQUAL: :tEQQ,
63
+ EQUAL_GREATER: :tASSOC,
64
+ EQUAL_TILDE: :tMATCH,
65
+ FLOAT: :tFLOAT,
66
+ FLOAT_IMAGINARY: :tIMAGINARY,
67
+ FLOAT_RATIONAL: :tRATIONAL,
68
+ FLOAT_RATIONAL_IMAGINARY: :tIMAGINARY,
69
+ GLOBAL_VARIABLE: :tGVAR,
70
+ GREATER: :tGT,
71
+ GREATER_EQUAL: :tGEQ,
72
+ GREATER_GREATER: :tRSHFT,
73
+ GREATER_GREATER_EQUAL: :tOP_ASGN,
74
+ HEREDOC_START: :tSTRING_BEG,
75
+ HEREDOC_END: :tSTRING_END,
76
+ IDENTIFIER: :tIDENTIFIER,
77
+ INSTANCE_VARIABLE: :tIVAR,
78
+ INTEGER: :tINTEGER,
79
+ INTEGER_IMAGINARY: :tIMAGINARY,
80
+ INTEGER_RATIONAL: :tRATIONAL,
81
+ INTEGER_RATIONAL_IMAGINARY: :tIMAGINARY,
82
+ KEYWORD_ALIAS: :kALIAS,
83
+ KEYWORD_AND: :kAND,
84
+ KEYWORD_BEGIN: :kBEGIN,
85
+ KEYWORD_BEGIN_UPCASE: :klBEGIN,
86
+ KEYWORD_BREAK: :kBREAK,
87
+ KEYWORD_CASE: :kCASE,
88
+ KEYWORD_CLASS: :kCLASS,
89
+ KEYWORD_DEF: :kDEF,
90
+ KEYWORD_DEFINED: :kDEFINED,
91
+ KEYWORD_DO: :kDO,
92
+ KEYWORD_DO_BLOCK: :kDO_BLOCK,
93
+ KEYWORD_DO_LAMBDA: :kDO_LAMBDA,
94
+ KEYWORD_DO_LOOP: :kDO_COND,
95
+ KEYWORD_END: :kEND,
96
+ KEYWORD_END_UPCASE: :klEND,
97
+ KEYWORD_ENSURE: :kENSURE,
98
+ KEYWORD_ELSE: :kELSE,
99
+ KEYWORD_ELSIF: :kELSIF,
100
+ KEYWORD_FALSE: :kFALSE,
101
+ KEYWORD_FOR: :kFOR,
102
+ KEYWORD_IF: :kIF,
103
+ KEYWORD_IF_MODIFIER: :kIF_MOD,
104
+ KEYWORD_IN: :kIN,
105
+ KEYWORD_MODULE: :kMODULE,
106
+ KEYWORD_NEXT: :kNEXT,
107
+ KEYWORD_NIL: :kNIL,
108
+ KEYWORD_NOT: :kNOT,
109
+ KEYWORD_OR: :kOR,
110
+ KEYWORD_REDO: :kREDO,
111
+ KEYWORD_RESCUE: :kRESCUE,
112
+ KEYWORD_RESCUE_MODIFIER: :kRESCUE_MOD,
113
+ KEYWORD_RETRY: :kRETRY,
114
+ KEYWORD_RETURN: :kRETURN,
115
+ KEYWORD_SELF: :kSELF,
116
+ KEYWORD_SUPER: :kSUPER,
117
+ KEYWORD_THEN: :kTHEN,
118
+ KEYWORD_TRUE: :kTRUE,
119
+ KEYWORD_UNDEF: :kUNDEF,
120
+ KEYWORD_UNLESS: :kUNLESS,
121
+ KEYWORD_UNLESS_MODIFIER: :kUNLESS_MOD,
122
+ KEYWORD_UNTIL: :kUNTIL,
123
+ KEYWORD_UNTIL_MODIFIER: :kUNTIL_MOD,
124
+ KEYWORD_WHEN: :kWHEN,
125
+ KEYWORD_WHILE: :kWHILE,
126
+ KEYWORD_WHILE_MODIFIER: :kWHILE_MOD,
127
+ KEYWORD_YIELD: :kYIELD,
128
+ KEYWORD___ENCODING__: :k__ENCODING__,
129
+ KEYWORD___FILE__: :k__FILE__,
130
+ KEYWORD___LINE__: :k__LINE__,
131
+ LABEL: :tLABEL,
132
+ LABEL_END: :tLABEL_END,
133
+ LAMBDA_BEGIN: :tLAMBEG,
134
+ LESS: :tLT,
135
+ LESS_EQUAL: :tLEQ,
136
+ LESS_EQUAL_GREATER: :tCMP,
137
+ LESS_LESS: :tLSHFT,
138
+ LESS_LESS_EQUAL: :tOP_ASGN,
139
+ METHOD_NAME: :tFID,
140
+ MINUS: :tMINUS,
141
+ MINUS_EQUAL: :tOP_ASGN,
142
+ MINUS_GREATER: :tLAMBDA,
143
+ NEWLINE: :tNL,
144
+ NEWLINE_TERMINATOR: :tNL,
145
+ NUMBERED_REFERENCE: :tNTH_REF,
146
+ PARENTHESIS_LEFT: :tLPAREN2,
147
+ PARENTHESIS_LEFT_GROUPING: :tLPAREN,
148
+ PARENTHESIS_LEFT_PARENTHESES: :tLPAREN_ARG,
149
+ PARENTHESIS_RIGHT: :tRPAREN,
150
+ PERCENT: :tPERCENT,
151
+ PERCENT_EQUAL: :tOP_ASGN,
152
+ PERCENT_LOWER_I: :tQSYMBOLS_BEG,
153
+ PERCENT_LOWER_W: :tQWORDS_BEG,
154
+ PERCENT_UPPER_I: :tSYMBOLS_BEG,
155
+ PERCENT_UPPER_W: :tWORDS_BEG,
156
+ PERCENT_LOWER_X: :tXSTRING_BEG,
157
+ PLUS: :tPLUS,
158
+ PLUS_EQUAL: :tOP_ASGN,
159
+ PIPE_EQUAL: :tOP_ASGN,
160
+ PIPE: :tPIPE,
161
+ PIPE_PIPE: :tOROP,
162
+ PIPE_PIPE_EQUAL: :tOP_ASGN,
163
+ QUESTION_MARK: :tEH,
164
+ REGEXP_BEGIN: :tREGEXP_BEG,
165
+ REGEXP_END: :tSTRING_END,
166
+ SEMICOLON: :tSEMI,
167
+ SLASH: :tDIVIDE,
168
+ SLASH_EQUAL: :tOP_ASGN,
169
+ STAR: :tSTAR2,
170
+ STAR_EQUAL: :tOP_ASGN,
171
+ STAR_STAR: :tPOW,
172
+ STAR_STAR_EQUAL: :tOP_ASGN,
173
+ STRING_BEGIN: :tSTRING_BEG,
174
+ STRING_CONTENT: :tSTRING_CONTENT,
175
+ STRING_END: :tSTRING_END,
176
+ SYMBOL_BEGIN: :tSYMBEG,
177
+ TILDE: :tTILDE,
178
+ UAMPERSAND: :tAMPER,
179
+ UCOLON_COLON: :tCOLON3,
180
+ UDOT_DOT: :tBDOT2,
181
+ UDOT_DOT_DOT: :tBDOT3,
182
+ UMINUS: :tUMINUS,
183
+ UMINUS_NUM: :tUNARY_NUM,
184
+ UPLUS: :tUPLUS,
185
+ USTAR: :tSTAR,
186
+ USTAR_STAR: :tDSTAR,
187
+ WORDS_SEP: :tSPACE,
188
+ WORDS_SEP_IMPLICIT: :tSPACE,
189
+ XSTRING_BEGIN: :tXSTRING_BEG
190
+ }
191
+
192
+ # Heredocs are complex and require us to keep track of a bit of info to refer to later
193
+ HeredocData = Struct.new(:identifier, :common_whitespace, keyword_init: true)
194
+
195
+ private_constant :TYPES, :HeredocData
196
+
197
+ # The Parser::Source::Buffer that the tokens were lexed from.
198
+ attr_reader :source_buffer
199
+
200
+ # An array of tuples that contain prism tokens and their associated lex
201
+ # state when they were lexed.
202
+ attr_reader :lexed
203
+
204
+ # A hash that maps offsets in bytes to offsets in characters.
205
+ attr_reader :offset_cache
206
+
207
+ # Initialize the lexer with the given source buffer, prism tokens, and
208
+ # offset cache.
209
+ def initialize(source_buffer, lexed, offset_cache)
210
+ @source_buffer = source_buffer
211
+ @lexed = lexed
212
+ @offset_cache = offset_cache
213
+ end
214
+
215
+ Range = ::Parser::Source::Range
216
+ private_constant :Range
217
+
218
+ # Convert the prism tokens into the expected format for the parser gem.
219
+ def to_a
220
+ tokens = []
221
+
222
+ index = 0
223
+ length = lexed.length
224
+
225
+ heredoc_stack = []
226
+ quote_stack = []
227
+
228
+ # The parser gem emits the newline tokens for comments out of order. This saves
229
+ # that token location to emit at a later time to properly line everything up.
230
+ # https://github.com/whitequark/parser/issues/1025
231
+ comment_newline_location = nil
232
+
233
+ while index < length
234
+ token = lexed[index]
235
+ index += 1
236
+ next if TYPES_ALWAYS_SKIP.include?(token.type)
237
+
238
+ type = TYPES.fetch(token.type)
239
+ value = token.value
240
+ location = range(token.location.start_offset, token.location.end_offset)
241
+
242
+ # A newline deferred past a run of comments is emitted before the
243
+ # token that follows the last comment.
244
+ if comment_newline_location && type != :tCOMMENT
245
+ tokens << [:tNL, [nil, comment_newline_location]]
246
+ comment_newline_location = nil
247
+ end
248
+
249
+ case type
250
+ when :tCHARACTER
251
+ value.delete_prefix!("?")
252
+ # Character literals behave similar to double-quoted strings. We can use the same escaping mechanism.
253
+ value = unescape_string(value, "?")
254
+ when :tCOMMENT
255
+ if token.type == :EMBDOC_BEGIN
256
+
257
+ while !((next_token = lexed[index]) && next_token.type == :EMBDOC_END) && (index < length - 1)
258
+ value += next_token.value
259
+ index += 1
260
+ end
261
+
262
+ value += next_token.value
263
+ location = range(token.location.start_offset, next_token.location.end_offset)
264
+ index += 1
265
+ else
266
+ # A carriage return before the terminating newline is part of
267
+ # the comment token but not of the comment's value.
268
+ location = range(token.location.start_offset, token.location.end_offset - 1) if value.chomp!
269
+ end
270
+ when :tNL
271
+ next_token = lexed[index]
272
+ # Newlines after comments are emitted out of order.
273
+ if next_token&.type == :COMMENT
274
+ comment_newline_location = location
275
+ next
276
+ end
277
+
278
+ value = nil
279
+ when :tFLOAT
280
+ value = parse_float(value)
281
+ when :tIMAGINARY
282
+ value = parse_complex(value)
283
+ when :tINTEGER
284
+ if value.start_with?("+")
285
+ tokens << [:tUNARY_NUM, ["+", range(token.location.start_offset, token.location.start_offset + 1)]]
286
+ location = range(token.location.start_offset + 1, token.location.end_offset)
287
+ end
288
+
289
+ value = parse_integer(value)
290
+ when :tLABEL
291
+ value.chomp!(":")
292
+ when :tLABEL_END
293
+ value.chomp!(":")
294
+ when :tNTH_REF
295
+ value = parse_integer(value.delete_prefix("$"))
296
+ when :tOP_ASGN
297
+ value.chomp!("=")
298
+ when :tRATIONAL
299
+ value = parse_rational(value)
300
+ when :tSPACE
301
+ location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value))
302
+ value = nil
303
+ when :tSTRING_BEG
304
+ next_token = lexed[index]
305
+ next_next_token = lexed[index + 1]
306
+ basic_quotes = value == '"' || value == "'"
307
+
308
+ if basic_quotes && next_token&.type == :STRING_END
309
+ next_location = token.location.join(next_token.location)
310
+ type = :tSTRING
311
+ value = ""
312
+ location = range(next_location.start_offset, next_location.end_offset)
313
+ index += 1
314
+ elsif value.start_with?("'", '"', "%")
315
+ if next_token&.type == :STRING_CONTENT && next_next_token&.type == :STRING_END
316
+ string_value = next_token.value
317
+ if simplify_string?(string_value, value)
318
+ next_location = token.location.join(next_next_token.location)
319
+ if percent_array?(value)
320
+ value = percent_array_unescape(string_value)
321
+ else
322
+ value = unescape_string(string_value, value)
323
+ end
324
+ type = :tSTRING
325
+ location = range(next_location.start_offset, next_location.end_offset)
326
+ index += 2
327
+ tokens << [type, [value, location]]
328
+
329
+ next
330
+ end
331
+ end
332
+
333
+ quote_stack.push(value)
334
+ elsif token.type == :HEREDOC_START
335
+ quote = value[2] == "-" || value[2] == "~" ? value[3] : value[2]
336
+ heredoc_type = value[2] == "-" || value[2] == "~" ? value[2] : ""
337
+ heredoc = HeredocData.new(
338
+ identifier: value.match(/<<[-~]?["'`]?(?<heredoc_identifier>.*?)["'`]?\z/)[:heredoc_identifier],
339
+ common_whitespace: 0,
340
+ )
341
+
342
+ if quote == "`"
343
+ type = :tXSTRING_BEG
344
+ end
345
+
346
+ # The parser gem trims whitespace from squiggly heredocs. We must record
347
+ # the most common whitespace to later remove.
348
+ if heredoc_type == "~" || heredoc_type == "`"
349
+ heredoc.common_whitespace = calculate_heredoc_whitespace(index)
350
+ end
351
+
352
+ if quote == "'" || quote == '"' || quote == "`"
353
+ value = "<<#{quote}"
354
+ else
355
+ value = '<<"'
356
+ end
357
+
358
+ heredoc_stack.push(heredoc)
359
+ quote_stack.push(value)
360
+ end
361
+ when :tSTRING_CONTENT
362
+ is_percent_array = percent_array?(quote_stack.last)
363
+
364
+ if (lines = token.value.lines).one?
365
+ # Prism usually emits a single token for strings with line continuations.
366
+ # For squiggly heredocs they are not joined so we do that manually here.
367
+ current_string = +""
368
+ current_length = 0
369
+ start_offset = token.location.start_offset
370
+ while token.type == :STRING_CONTENT
371
+ current_length += token.value.bytesize
372
+ # Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line.
373
+ prev_token = lexed[index - 2] if index - 2 >= 0
374
+ is_first_token_on_line = prev_token && token.location.start_line != prev_token.location.start_line
375
+ # The parser gem only removes indentation when the heredoc is not nested
376
+ not_nested = heredoc_stack.size == 1
377
+ if is_percent_array
378
+ value = percent_array_unescape(token.value)
379
+ elsif is_first_token_on_line && not_nested && (current_heredoc = heredoc_stack.last).common_whitespace > 0
380
+ value = trim_heredoc_whitespace(token.value, current_heredoc)
381
+ end
382
+
383
+ current_string << unescape_string(value, quote_stack.last)
384
+ relevant_backslash_count = if quote_stack.last.start_with?("%W", "%I")
385
+ 0 # the last backslash escapes the newline
386
+ else
387
+ token.value[/(\\{1,})\n/, 1]&.length || 0
388
+ end
389
+ if relevant_backslash_count.even? || !interpolation?(quote_stack.last)
390
+ tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]]
391
+ break
392
+ end
393
+ token = lexed[index]
394
+ index += 1
395
+ end
396
+ else
397
+ # When the parser gem encounters a line continuation inside of a multiline string,
398
+ # it emits a single string node. The backslash (and remaining newline) is removed.
399
+ current_line = +""
400
+ adjustment = 0
401
+ start_offset = token.location.start_offset
402
+ emit = false
403
+
404
+ lines.each.with_index do |line, index|
405
+ chomped_line = line.chomp
406
+ backslash_count = chomped_line[/\\{1,}\z/]&.length || 0
407
+ is_interpolation = interpolation?(quote_stack.last)
408
+
409
+ if backslash_count.odd? && (is_interpolation || is_percent_array)
410
+ if is_percent_array
411
+ current_line << percent_array_unescape(line)
412
+ adjustment += 1
413
+ else
414
+ chomped_line.delete_suffix!("\\")
415
+ current_line << chomped_line
416
+ adjustment += 2
417
+ end
418
+ # If the string ends with a line continuation emit the remainder
419
+ emit = index == lines.count - 1
420
+ else
421
+ current_line << line
422
+ emit = true
423
+ end
424
+
425
+ if emit
426
+ end_offset = start_offset + current_line.bytesize + adjustment
427
+ tokens << [:tSTRING_CONTENT, [unescape_string(current_line, quote_stack.last), range(start_offset, end_offset)]]
428
+ start_offset = end_offset
429
+ current_line = +""
430
+ adjustment = 0
431
+ end
432
+ end
433
+ end
434
+ next
435
+ when :tSTRING_DVAR
436
+ value = nil
437
+ when :tSTRING_END
438
+ if token.type == :HEREDOC_END && value.end_with?("\n")
439
+ newline_length = value.end_with?("\r\n") ? 2 : 1
440
+ value = heredoc_stack.pop.identifier
441
+ location = range(token.location.start_offset, token.location.end_offset - newline_length)
442
+ elsif token.type == :REGEXP_END
443
+ value = value[0]
444
+ location = range(token.location.start_offset, token.location.start_offset + 1)
445
+ end
446
+
447
+ quote_stack.pop
448
+ when :tSYMBEG
449
+ if (next_token = lexed[index]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END
450
+ next_location = token.location.join(next_token.location)
451
+ type = :tSYMBOL
452
+ value = next_token.value
453
+ value = { "~@" => "~", "!@" => "!" }.fetch(value, value)
454
+ location = range(next_location.start_offset, next_location.end_offset)
455
+ index += 1
456
+ else
457
+ quote_stack.push(value)
458
+ end
459
+ when :tFID
460
+ if !tokens.empty? && tokens.dig(-1, 0) == :kDEF
461
+ type = :tIDENTIFIER
462
+ end
463
+ when :tXSTRING_BEG
464
+ quote_stack.push(value)
465
+ when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG
466
+ # The separator that delimits the opener from the first word is
467
+ # part of the opener for parser, so it is not emitted separately.
468
+ if (next_token = lexed[index]) && %i[WORDS_SEP WORDS_SEP_IMPLICIT].include?(next_token.type)
469
+ index += 1
470
+ end
471
+
472
+ quote_stack.push(value)
473
+ when :tREGEXP_BEG
474
+ quote_stack.push(value)
475
+ end
476
+
477
+ tokens << [type, [value, location]]
478
+
479
+ if token.type == :REGEXP_END
480
+ tokens << [:tREGEXP_OPT, [token.value[1..], range(token.location.start_offset + 1, token.location.end_offset)]]
481
+ end
482
+ end
483
+
484
+ if comment_newline_location
485
+ tokens << [:tNL, [nil, comment_newline_location]]
486
+ end
487
+
488
+ tokens
489
+ end
490
+
491
+ private
492
+
493
+ # Creates a new parser range, taking prisms byte offsets into account
494
+ def range(start_offset, end_offset)
495
+ Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset])
496
+ end
497
+
498
+ # Parse an integer from the string representation.
499
+ def parse_integer(value)
500
+ Integer(value)
501
+ rescue ArgumentError
502
+ 0
503
+ end
504
+
505
+ # Parse a float from the string representation.
506
+ def parse_float(value)
507
+ Float(value)
508
+ rescue ArgumentError
509
+ 0.0
510
+ end
511
+
512
+ # Parse a complex from the string representation.
513
+ def parse_complex(value)
514
+ value.chomp!("i")
515
+
516
+ if value.end_with?("r")
517
+ Complex(0, parse_rational(value))
518
+ elsif value.start_with?(/0[BbOoDdXx]/)
519
+ Complex(0, parse_integer(value))
520
+ else
521
+ Complex(0, value)
522
+ end
523
+ rescue ArgumentError
524
+ 0i
525
+ end
526
+
527
+ # Parse a rational from the string representation.
528
+ def parse_rational(value)
529
+ value.chomp!("r")
530
+
531
+ if value.start_with?(/0[BbOoDdXx]/)
532
+ Rational(parse_integer(value))
533
+ else
534
+ Rational(value)
535
+ end
536
+ rescue ArgumentError
537
+ 0r
538
+ end
539
+
540
+ # Wonky heredoc tab/spaces rules.
541
+ # https://github.com/ruby/prism/blob/v1.3.0/src/prism.c#L10548-L10558
542
+ def calculate_heredoc_whitespace(heredoc_token_index)
543
+ next_token_index = heredoc_token_index
544
+ nesting_level = 0
545
+ previous_line = -1
546
+ result = Float::MAX
547
+
548
+ while (next_token = lexed[next_token_index])
549
+ next_token_index += 1
550
+ next_next_token = lexed[next_token_index]
551
+ first_token_on_line = next_token.location.start_column == 0
552
+
553
+ # String content inside nested heredocs and interpolation is ignored
554
+ if next_token.type == :HEREDOC_START || next_token.type == :EMBEXPR_BEGIN
555
+ # When interpolation is the first token of a line there is no string
556
+ # content to check against. There will be no common whitespace.
557
+ if nesting_level == 0 && first_token_on_line
558
+ result = 0
559
+ end
560
+ nesting_level += 1
561
+ elsif next_token.type == :HEREDOC_END || next_token.type == :EMBEXPR_END
562
+ nesting_level -= 1
563
+ # When we encountered the matching heredoc end, we can exit
564
+ break if nesting_level == -1
565
+ elsif next_token.type == :STRING_CONTENT && nesting_level == 0 && first_token_on_line
566
+ common_whitespace = 0
567
+ next_token.value[/^\s*/].each_char do |char|
568
+ if char == "\t"
569
+ common_whitespace = (common_whitespace / 8 + 1) * 8;
570
+ else
571
+ common_whitespace += 1
572
+ end
573
+ end
574
+
575
+ is_first_token_on_line = next_token.location.start_line != previous_line
576
+ # Whitespace is significant if followed by interpolation
577
+ whitespace_only = common_whitespace == next_token.value.length && next_next_token&.location&.start_line != next_token.location.start_line
578
+ if is_first_token_on_line && !whitespace_only && common_whitespace < result
579
+ result = common_whitespace
580
+ previous_line = next_token.location.start_line
581
+ end
582
+ end
583
+ end
584
+ result
585
+ end
586
+
587
+ # Wonky heredoc tab/spaces rules.
588
+ # https://github.com/ruby/prism/blob/v1.3.0/src/prism.c#L16528-L16545
589
+ def trim_heredoc_whitespace(string, heredoc)
590
+ trimmed_whitespace = 0
591
+ trimmed_characters = 0
592
+ while (string[trimmed_characters] == "\t" || string[trimmed_characters] == " ") && trimmed_whitespace < heredoc.common_whitespace
593
+ if string[trimmed_characters] == "\t"
594
+ trimmed_whitespace = (trimmed_whitespace / 8 + 1) * 8;
595
+ break if trimmed_whitespace > heredoc.common_whitespace
596
+ else
597
+ trimmed_whitespace += 1
598
+ end
599
+ trimmed_characters += 1
600
+ end
601
+
602
+ string[trimmed_characters..]
603
+ end
604
+
605
+ # Escape sequences that have special and should appear unescaped in the resulting string.
606
+ ESCAPES = {
607
+ "a" => "\a", "b" => "\b", "e" => "\e", "f" => "\f",
608
+ "n" => "\n", "r" => "\r", "s" => "\s", "t" => "\t",
609
+ "v" => "\v", "\\" => "\\"
610
+ }.freeze
611
+ private_constant :ESCAPES
612
+
613
+ # When one of these delimiters is encountered, then the other
614
+ # one is allowed to be escaped as well.
615
+ DELIMITER_SYMETRY = { "[" => "]", "(" => ")", "{" => "}", "<" => ">" }.freeze
616
+ private_constant :DELIMITER_SYMETRY
617
+
618
+
619
+ # https://github.com/whitequark/parser/blob/v3.3.6.0/lib/parser/lexer-strings.rl#L14
620
+ REGEXP_META_CHARACTERS = ["\\", "$", "(", ")", "*", "+", ".", "<", ">", "?", "[", "]", "^", "{", "|", "}"]
621
+ private_constant :REGEXP_META_CHARACTERS
622
+
623
+ # Apply Ruby string escaping rules
624
+ def unescape_string(string, quote)
625
+ # In single-quoted heredocs, everything is taken literally.
626
+ return string if quote == "<<'"
627
+
628
+ # OPTIMIZATION: Assume that few strings need escaping to speed up the common case.
629
+ return string unless string.include?("\\")
630
+
631
+ # Enclosing character for the string. `"` for `"foo"`, `{` for `%w{foo}`, etc.
632
+ delimiter = quote[-1]
633
+
634
+ if regexp?(quote)
635
+ # Should be escaped handled to single-quoted heredocs. The only character that is
636
+ # allowed to be escaped is the delimiter, except when that also has special meaning
637
+ # in the regexp. Since all the symetry delimiters have special meaning, they don't need
638
+ # to be considered separately.
639
+ if REGEXP_META_CHARACTERS.include?(delimiter)
640
+ string
641
+ else
642
+ # There can never be an even amount of backslashes. It would be a syntax error.
643
+ string.gsub(/\\(#{Regexp.escape(delimiter)})/, '\1')
644
+ end
645
+ elsif interpolation?(quote)
646
+ # Appending individual escape sequences may force the string out of its intended
647
+ # encoding. Start out with binary and force it back later.
648
+ result = "".b
649
+
650
+ scanner = StringScanner.new(string)
651
+ while (skipped = scanner.skip_until(/\\/))
652
+ # Append what was just skipped over, excluding the found backslash.
653
+ result.append_as_bytes(string.byteslice(scanner.pos - skipped, skipped - 1))
654
+ escape_read(result, scanner, false, false)
655
+ end
656
+
657
+ # Add remaining chars
658
+ result.append_as_bytes(string.byteslice(scanner.pos..))
659
+ result.force_encoding(source_buffer.source.encoding)
660
+ else
661
+ delimiters = Regexp.escape("#{delimiter}#{DELIMITER_SYMETRY[delimiter]}")
662
+ string.gsub(/\\([\\#{delimiters}])/, '\1')
663
+ end
664
+ end
665
+
666
+ # Certain strings are merged into a single string token.
667
+ def simplify_string?(value, quote)
668
+ case quote
669
+ when "'"
670
+ # Only simplify 'foo'
671
+ !value.include?("\n")
672
+ when '"'
673
+ # Simplify when every line ends with a line continuation, or it is the last line
674
+ value.lines.all? do |line|
675
+ !line.end_with?("\n") || line[/(\\*)$/, 1]&.length&.odd?
676
+ end
677
+ else
678
+ # %q and similar are never simplified
679
+ false
680
+ end
681
+ end
682
+
683
+ # Escape a byte value, given the control and meta flags.
684
+ def escape_build(value, control, meta)
685
+ value &= 0x9f if control
686
+ value |= 0x80 if meta
687
+ value
688
+ end
689
+
690
+ # Read an escape out of the string scanner, given the control and meta
691
+ # flags, and push the unescaped value into the result.
692
+ def escape_read(result, scanner, control, meta)
693
+ if scanner.skip("\n")
694
+ # Line continuation
695
+ elsif (value = ESCAPES[scanner.peek(1)])
696
+ # Simple single-character escape sequences like \n
697
+ result.append_as_bytes(value)
698
+ scanner.pos += 1
699
+ elsif (value = scanner.scan(/[0-7]{1,3}/))
700
+ # \nnn
701
+ result.append_as_bytes(escape_build(value.to_i(8), control, meta))
702
+ elsif (value = scanner.scan(/x[0-9a-fA-F]{1,2}/))
703
+ # \xnn
704
+ result.append_as_bytes(escape_build(value[1..].to_i(16), control, meta))
705
+ elsif (value = scanner.scan(/u[0-9a-fA-F]{4}/))
706
+ # \unnnn
707
+ result.append_as_bytes(value[1..].hex.chr(Encoding::UTF_8))
708
+ elsif scanner.skip("u{}")
709
+ # https://github.com/whitequark/parser/issues/856
710
+ elsif (value = scanner.scan(/u{.*?}/))
711
+ # \u{nnnn ...}
712
+ value[2..-2].split.each do |unicode|
713
+ result.append_as_bytes(unicode.hex.chr(Encoding::UTF_8))
714
+ end
715
+ elsif (value = scanner.scan(/c\\?(?=[[:print:]])|C-\\?(?=[[:print:]])/))
716
+ # \cx or \C-x where x is an ASCII printable character
717
+ escape_read(result, scanner, true, meta)
718
+ elsif (value = scanner.scan(/M-\\?(?=[[:print:]])/))
719
+ # \M-x where x is an ASCII printable character
720
+ escape_read(result, scanner, control, true)
721
+ elsif (byte = scanner.scan_byte)
722
+ # Something else after an escape.
723
+ if control && byte == 0x3f # ASCII '?'
724
+ result.append_as_bytes(escape_build(0x7f, false, meta))
725
+ else
726
+ result.append_as_bytes(escape_build(byte, control, meta))
727
+ end
728
+ end
729
+ end
730
+
731
+ # In a percent array, certain whitespace can be preceeded with a backslash,
732
+ # causing the following characters to be part of the previous element.
733
+ def percent_array_unescape(string)
734
+ string.gsub(/(\\)+[ \f\n\r\t\v]/) do |full_match|
735
+ full_match.delete_prefix!("\\") if Regexp.last_match[1].length.odd?
736
+ full_match
737
+ end
738
+ end
739
+
740
+ # For %-arrays whitespace, the parser gem only considers whitespace before the newline.
741
+ def percent_array_leading_whitespace(string)
742
+ return 1 if string.start_with?("\n")
743
+
744
+ leading_whitespace = 0
745
+ string.each_char do |c|
746
+ break if c == "\n"
747
+ leading_whitespace += 1
748
+ end
749
+ leading_whitespace
750
+ end
751
+
752
+ # Determine if characters preceeded by a backslash should be escaped or not
753
+ def interpolation?(quote)
754
+ !quote.end_with?("'") && !quote.start_with?("%q", "%w", "%i", "%s")
755
+ end
756
+
757
+ # Regexp allow interpolation but are handled differently during unescaping
758
+ def regexp?(quote)
759
+ quote == "/" || quote.start_with?("%r")
760
+ end
761
+
762
+ # Determine if the string is part of a %-style array.
763
+ def percent_array?(quote)
764
+ quote.start_with?("%w", "%W", "%i", "%I")
765
+ end
766
+ end
767
+ end
768
+ end
769
+ end