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.
- checksums.yaml +7 -0
- data/BSDmakefile +58 -0
- data/CHANGELOG.md +870 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +58 -0
- data/LICENSE.md +7 -0
- data/Makefile +145 -0
- data/README.md +81 -0
- data/config.yml +4752 -0
- data/docs/build_system.md +119 -0
- data/docs/configuration.md +69 -0
- data/docs/cruby_compilation.md +27 -0
- data/docs/design.md +53 -0
- data/docs/encoding.md +121 -0
- data/docs/fuzzing.md +81 -0
- data/docs/heredocs.md +36 -0
- data/docs/javascript.md +133 -0
- data/docs/local_variable_depth.md +229 -0
- data/docs/mapping.md +117 -0
- data/docs/parser_translation.md +24 -0
- data/docs/parsing_rules.md +22 -0
- data/docs/releasing.md +147 -0
- data/docs/relocation.md +32 -0
- data/docs/ripper_translation.md +63 -0
- data/docs/ruby_api.md +45 -0
- data/docs/ruby_parser_translation.md +19 -0
- data/docs/serialization.md +230 -0
- data/docs/testing.md +72 -0
- data/ext/prism/api_node.c +7005 -0
- data/ext/prism/extconf.rb +126 -0
- data/ext/prism/extension.c +1783 -0
- data/ext/prism/extension.h +19 -0
- data/include/prism/arena.h +37 -0
- data/include/prism/ast.h +10371 -0
- data/include/prism/buffer.h +52 -0
- data/include/prism/comments.h +55 -0
- data/include/prism/compiler/accel.h +24 -0
- data/include/prism/compiler/align.h +36 -0
- data/include/prism/compiler/assume.h +25 -0
- data/include/prism/compiler/exported.h +24 -0
- data/include/prism/compiler/fallthrough.h +22 -0
- data/include/prism/compiler/filesystem.h +32 -0
- data/include/prism/compiler/flex_array.h +19 -0
- data/include/prism/compiler/force_inline.h +21 -0
- data/include/prism/compiler/format.h +25 -0
- data/include/prism/compiler/inline.h +17 -0
- data/include/prism/compiler/nodiscard.h +22 -0
- data/include/prism/compiler/nonnull.h +18 -0
- data/include/prism/compiler/unused.h +18 -0
- data/include/prism/constant_pool.h +81 -0
- data/include/prism/diagnostic.h +93 -0
- data/include/prism/errors_format.h +53 -0
- data/include/prism/excludes.h +29 -0
- data/include/prism/integer.h +41 -0
- data/include/prism/internal/allocator.h +68 -0
- data/include/prism/internal/allocator_debug.h +88 -0
- data/include/prism/internal/arena.h +108 -0
- data/include/prism/internal/bit.h +42 -0
- data/include/prism/internal/buffer.h +91 -0
- data/include/prism/internal/char.h +145 -0
- data/include/prism/internal/comments.h +53 -0
- data/include/prism/internal/constant_pool.h +152 -0
- data/include/prism/internal/diagnostic.h +389 -0
- data/include/prism/internal/encoding.h +242 -0
- data/include/prism/internal/integer.h +68 -0
- data/include/prism/internal/isinf.h +16 -0
- data/include/prism/internal/line_offset_list.h +34 -0
- data/include/prism/internal/list.h +62 -0
- data/include/prism/internal/magic_comments.h +23 -0
- data/include/prism/internal/memchr.h +15 -0
- data/include/prism/internal/node.h +38 -0
- data/include/prism/internal/options.h +232 -0
- data/include/prism/internal/parser.h +984 -0
- data/include/prism/internal/regexp.h +26 -0
- data/include/prism/internal/serialize.h +39 -0
- data/include/prism/internal/source.h +72 -0
- data/include/prism/internal/static_literals.h +100 -0
- data/include/prism/internal/stringy.h +35 -0
- data/include/prism/internal/strncasecmp.h +18 -0
- data/include/prism/internal/strpbrk.h +33 -0
- data/include/prism/internal/tokens.h +11 -0
- data/include/prism/json.h +32 -0
- data/include/prism/line_offset_list.h +61 -0
- data/include/prism/magic_comments.h +35 -0
- data/include/prism/node.h +94 -0
- data/include/prism/options.h +354 -0
- data/include/prism/parser.h +404 -0
- data/include/prism/prettyprint.h +31 -0
- data/include/prism/serialize.h +115 -0
- data/include/prism/source.h +155 -0
- data/include/prism/stream.h +28 -0
- data/include/prism/string_query.h +63 -0
- data/include/prism/stringy.h +72 -0
- data/include/prism/version.h +38 -0
- data/include/prism.h +131 -0
- data/lib/prism/3.3/prism.so +0 -0
- data/lib/prism/3.4/prism.so +0 -0
- data/lib/prism/4.0/prism.so +0 -0
- data/lib/prism/compiler.rb +815 -0
- data/lib/prism/desugar_compiler.rb +463 -0
- data/lib/prism/dispatcher.rb +2383 -0
- data/lib/prism/dot_visitor.rb +4848 -0
- data/lib/prism/dsl.rb +1369 -0
- data/lib/prism/ffi.rb +740 -0
- data/lib/prism/inspect_visitor.rb +2425 -0
- data/lib/prism/lex_compat.rb +953 -0
- data/lib/prism/mutation_compiler.rb +780 -0
- data/lib/prism/node.rb +33204 -0
- data/lib/prism/node_ext.rb +504 -0
- data/lib/prism/node_find.rb +215 -0
- data/lib/prism/parse_result/errors.rb +72 -0
- data/lib/prism/parse_result/newlines.rb +423 -0
- data/lib/prism/parse_result.rb +1221 -0
- data/lib/prism/pattern.rb +314 -0
- data/lib/prism/polyfill/append_as_bytes.rb +15 -0
- data/lib/prism/polyfill/byteindex.rb +13 -0
- data/lib/prism/polyfill/scan_byte.rb +14 -0
- data/lib/prism/polyfill/unpack1.rb +14 -0
- data/lib/prism/polyfill/warn.rb +36 -0
- data/lib/prism/reflection.rb +427 -0
- data/lib/prism/relocation.rb +567 -0
- data/lib/prism/serialize.rb +5308 -0
- data/lib/prism/string_query.rb +46 -0
- data/lib/prism/translation/parser/builder.rb +70 -0
- data/lib/prism/translation/parser/compiler.rb +2226 -0
- data/lib/prism/translation/parser/lexer.rb +769 -0
- data/lib/prism/translation/parser.rb +376 -0
- data/lib/prism/translation/parser_current.rb +26 -0
- data/lib/prism/translation/parser_versions.rb +36 -0
- data/lib/prism/translation/ripper/filter.rb +53 -0
- data/lib/prism/translation/ripper/lexer.rb +133 -0
- data/lib/prism/translation/ripper/sexp.rb +118 -0
- data/lib/prism/translation/ripper/shim.rb +7 -0
- data/lib/prism/translation/ripper.rb +4278 -0
- data/lib/prism/translation/ruby_parser.rb +1721 -0
- data/lib/prism/translation.rb +20 -0
- data/lib/prism/visitor.rb +1440 -0
- data/lib/prism.rb +168 -0
- data/prism.gemspec +234 -0
- data/rbi/generated/prism/compiler.rbi +489 -0
- data/rbi/generated/prism/desugar_compiler.rbi +306 -0
- data/rbi/generated/prism/dispatcher.rbi +1138 -0
- data/rbi/generated/prism/dot_visitor.rbi +618 -0
- data/rbi/generated/prism/dsl.rbi +751 -0
- data/rbi/generated/prism/inspect_visitor.rbi +503 -0
- data/rbi/generated/prism/lex_compat.rbi +162 -0
- data/rbi/generated/prism/mutation_compiler.rbi +464 -0
- data/rbi/generated/prism/node.rbi +13681 -0
- data/rbi/generated/prism/node_ext.rbi +259 -0
- data/rbi/generated/prism/node_find.rbi +79 -0
- data/rbi/generated/prism/parse_result/errors.rbi +21 -0
- data/rbi/generated/prism/parse_result/newlines.rbi +208 -0
- data/rbi/generated/prism/parse_result.rbi +764 -0
- data/rbi/generated/prism/pattern.rbi +134 -0
- data/rbi/generated/prism/reflection.rbi +89 -0
- data/rbi/generated/prism/relocation.rbi +329 -0
- data/rbi/generated/prism/serialize.rbi +181 -0
- data/rbi/generated/prism/string_query.rbi +36 -0
- data/rbi/generated/prism/translation.rbi +7 -0
- data/rbi/generated/prism/visitor.rbi +650 -0
- data/rbi/generated/prism.rbi +86 -0
- data/rbi/prism/translation/parser.rbi +11 -0
- data/rbi/prism/translation/parser_versions.rbi +28 -0
- data/rbi/prism/translation/ripper.rbi +15 -0
- data/rbi/rubyvm/node_find.rbi +14 -0
- data/sig/generated/prism/compiler.rbs +492 -0
- data/sig/generated/prism/desugar_compiler.rbs +305 -0
- data/sig/generated/prism/dispatcher.rbs +1292 -0
- data/sig/generated/prism/dot_visitor.rbs +635 -0
- data/sig/generated/prism/dsl.rbs +928 -0
- data/sig/generated/prism/inspect_visitor.rbs +509 -0
- data/sig/generated/prism/lex_compat.rbs +166 -0
- data/sig/generated/prism/mutation_compiler.rbs +464 -0
- data/sig/generated/prism/node.rbs +20160 -0
- data/sig/generated/prism/node_ext.rbs +290 -0
- data/sig/generated/prism/node_find.rbs +90 -0
- data/sig/generated/prism/parse_result/errors.rbs +22 -0
- data/sig/generated/prism/parse_result/newlines.rbs +223 -0
- data/sig/generated/prism/parse_result.rbs +859 -0
- data/sig/generated/prism/pattern.rbs +156 -0
- data/sig/generated/prism/reflection.rbs +101 -0
- data/sig/generated/prism/relocation.rbs +428 -0
- data/sig/generated/prism/serialize.rbs +195 -0
- data/sig/generated/prism/string_query.rbs +36 -0
- data/sig/generated/prism/translation.rbs +8 -0
- data/sig/generated/prism/visitor.rbs +1111 -0
- data/sig/generated/prism.rbs +84 -0
- data/sig/manifest.yaml +2 -0
- data/src/arena.c +117 -0
- data/src/buffer.c +380 -0
- data/src/char.c +289 -0
- data/src/comments.c +454 -0
- data/src/constant_pool.c +406 -0
- data/src/diagnostic.c +886 -0
- data/src/encoding.c +5345 -0
- data/src/errors_format.c +519 -0
- data/src/integer.c +681 -0
- data/src/json.c +5733 -0
- data/src/line_offset_list.c +100 -0
- data/src/list.c +24 -0
- data/src/memchr.c +37 -0
- data/src/node.c +4515 -0
- data/src/options.c +451 -0
- data/src/parser.c +349 -0
- data/src/prettyprint.c +9024 -0
- data/src/prism.c +23739 -0
- data/src/regexp.c +1706 -0
- data/src/serialize.c +2309 -0
- data/src/source.c +518 -0
- data/src/static_literals.c +685 -0
- data/src/string_query.c +166 -0
- data/src/stringy.c +91 -0
- data/src/strncasecmp.c +37 -0
- data/src/strpbrk.c +447 -0
- data/src/tokens.c +724 -0
- 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
|