regexp_parser 2.12.0 → 2.13.0

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.
@@ -1,864 +0,0 @@
1
- %%{
2
- machine re_scanner;
3
- include re_char_type "char_type.rl";
4
- include re_property "property.rl";
5
-
6
- utf8_2_byte = (0xc2..0xdf 0x80..0xbf);
7
- utf8_3_byte = (0xe0..0xef 0x80..0xbf 0x80..0xbf);
8
- utf8_4_byte = (0xf0..0xf4 0x80..0xbf 0x80..0xbf 0x80..0xbf);
9
- utf8_multibyte = utf8_2_byte | utf8_3_byte | utf8_4_byte;
10
-
11
- dot = '.';
12
- backslash = '\\';
13
- alternation = '|';
14
- beginning_of_line = '^';
15
- end_of_line = '$';
16
-
17
- range_open = '{';
18
- range_close = '}';
19
- curlies = range_open | range_close;
20
-
21
- group_open = '(';
22
- group_close = ')';
23
- parentheses = group_open | group_close;
24
-
25
- set_open = '[';
26
- set_close = ']';
27
- brackets = set_open | set_close;
28
-
29
- comment = ('#' . [^\n]* . '\n'?);
30
-
31
- class_posix = ('[:' . '^'? . [^\[\]]* . ':]');
32
-
33
- line_anchor = beginning_of_line | end_of_line;
34
- anchor_char = [AbBzZG];
35
-
36
- escaped_ascii = [abefnrtv];
37
- octal_sequence = [0-7]{1,3};
38
-
39
- hex_sequence = 'x' . xdigit{1,2};
40
- hex_sequence_err = 'x' . [^0-9A-Fa-f];
41
- high_hex_sequence = 'x' . [89A-Fa-f] . xdigit . ( '\\x' . [89A-Fa-f] . xdigit )*;
42
-
43
- codepoint_single = 'u' . xdigit{4};
44
- codepoint_list = 'u{' . xdigit{1,6} . (space . xdigit{1,6})* . '}';
45
- codepoint_sequence = codepoint_single | codepoint_list;
46
-
47
- control_sequence = ('c' | 'C-') . (backslash . 'M-')? . backslash? . any;
48
-
49
- meta_sequence = 'M-' . (backslash . ('c' | 'C-'))? . backslash? . any;
50
-
51
- sequence_char = [CMcux];
52
-
53
- zero_or_one = '?' | '??' | '?+';
54
- zero_or_more = '*' | '*?' | '*+';
55
- one_or_more = '+' | '+?' | '++';
56
-
57
- quantifier_greedy = '?' | '*' | '+';
58
-
59
- quantity_exact = (digit+);
60
- quantity_minimum = (digit+) . ',';
61
- quantity_maximum = ',' . (digit+);
62
- quantity_range = (digit+) . ',' . (digit+);
63
- quantifier_interval = range_open . ( quantity_exact | quantity_minimum |
64
- quantity_maximum | quantity_range ) . range_close;
65
-
66
- conditional = '(?(';
67
-
68
- group_comment = '?#' . [^)]* . group_close;
69
-
70
- group_atomic = '?>';
71
- group_passive = '?:';
72
- group_absence = '?~';
73
-
74
- assertion_lookahead = '?=';
75
- assertion_nlookahead = '?!';
76
- assertion_lookbehind = '?<=';
77
- assertion_nlookbehind = '?<!';
78
-
79
- # try to treat every other group head as options group, like Ruby
80
- group_options = '?' . ( [^!#'():<=>~]+ . ':'? ) ?;
81
-
82
- group_name_id_ab = ([^!=0-9\->] | utf8_multibyte) . ([^>] | utf8_multibyte)*;
83
- group_name_id_sq = ([^0-9\-'] | utf8_multibyte) . ([^'] | utf8_multibyte)*;
84
- group_number = '-'? . [0-9]+;
85
- group_level = [+\-] . [0-9]+;
86
-
87
- group_name = ('<' . group_name_id_ab? . '>') |
88
- ("'" . group_name_id_sq? . "'");
89
- group_lookup = group_name | group_number;
90
-
91
- group_named = ('?' . group_name );
92
-
93
- group_ref_body = (('<' . (group_name_id_ab? | group_number) . group_level? '>') |
94
- ("'" . (group_name_id_sq? | group_number) . group_level? "'"));
95
-
96
- group_ref = 'k' . group_ref_body;
97
- group_call = 'g' . group_ref_body;
98
-
99
- group_type = group_atomic | group_passive | group_absence | group_named;
100
-
101
- keep_mark = 'K';
102
-
103
- assertion_type = assertion_lookahead | assertion_nlookahead |
104
- assertion_lookbehind | assertion_nlookbehind;
105
-
106
- # characters that 'break' a literal
107
- meta_char = dot | backslash | alternation |
108
- curlies | parentheses | brackets |
109
- line_anchor | quantifier_greedy;
110
-
111
- literal_delimiters = ']' | '}';
112
-
113
- ascii_print = ((0x20..0x7e) - meta_char - '#');
114
- ascii_nonprint = (0x01..0x1f | 0x7f);
115
-
116
- non_literal_escape = char_type_char | anchor_char | escaped_ascii |
117
- keep_mark | sequence_char;
118
-
119
- # escapes that also work within a character set
120
- set_escape = backslash | brackets | escaped_ascii |
121
- octal_sequence | property_char |
122
- sequence_char | single_codepoint_char_type;
123
-
124
-
125
- # EOF error, used where it can be detected
126
- action premature_end_error {
127
- text = copy(data, ts ? ts-1 : 0, -1)
128
- raise PrematureEndError.new(text)
129
- }
130
-
131
- # Invalid sequence error, used from sequences, like escapes and sets
132
- action invalid_sequence_error {
133
- text = copy(data, ts ? ts-1 : 0, -1)
134
- raise ValidationError.for(:sequence, 'sequence', text)
135
- }
136
-
137
- # group (nesting) and set open/close actions
138
- action group_opened { self.group_depth = group_depth + 1 }
139
- action group_closed { self.group_depth = group_depth - 1 }
140
- action set_opened { self.set_depth = set_depth + 1 }
141
- action set_closed { self.set_depth = set_depth - 1 }
142
-
143
- # Character set scanner, continues consuming characters until it meets the
144
- # closing bracket of the set.
145
- # --------------------------------------------------------------------------
146
- character_set := |*
147
- set_close > (set_meta, 2) @set_closed {
148
- emit(:set, :close, copy(data, ts, te))
149
- if in_set?
150
- fret;
151
- else
152
- fgoto main;
153
- end
154
- };
155
-
156
- '-]' @set_closed { # special case, emits two tokens
157
- emit(:literal, :literal, '-')
158
- emit(:set, :close, ']')
159
- if in_set?
160
- fret;
161
- else
162
- fgoto main;
163
- end
164
- };
165
-
166
- '-&&' { # special case, emits two tokens
167
- emit(:literal, :literal, '-')
168
- emit(:set, :intersection, '&&')
169
- };
170
-
171
- '^' {
172
- if prev_token[1] == :open
173
- emit(:set, :negate, '^')
174
- else
175
- emit(:literal, :literal, '^')
176
- end
177
- };
178
-
179
- '-' {
180
- # ranges cant start with the opening bracket, a subset, or
181
- # intersection/negation/range operators
182
- if prev_token[0] == :set
183
- emit(:literal, :literal, '-')
184
- else
185
- emit(:set, :range, '-')
186
- end
187
- };
188
-
189
- # Unlike ranges, intersections can start or end at set boundaries, whereupon
190
- # they match nothing: r = /[a&&]/; [r =~ ?a, r =~ ?&] # => [nil, nil]
191
- '&&' {
192
- emit(:set, :intersection, '&&')
193
- };
194
-
195
- backslash {
196
- fcall set_escape_sequence;
197
- };
198
-
199
- set_open >(open_bracket, 1) >set_opened {
200
- emit(:set, :open, '[')
201
- fcall character_set;
202
- };
203
-
204
- class_posix >(open_bracket, 1) @set_closed @eof(premature_end_error) {
205
- text = copy(data, ts, te)
206
-
207
- type = :posixclass
208
- class_name = text[2..-3]
209
- if class_name[0] == '^'
210
- class_name = class_name[1..-1]
211
- type = :nonposixclass
212
- end
213
-
214
- unless POSIX_CLASSES[class_name]
215
- raise ValidationError.for(:posix_class, text)
216
- end
217
-
218
- emit(type, class_name.to_sym, text)
219
- };
220
-
221
- meta_char > (set_meta, 1) {
222
- emit(:literal, :literal, copy(data, ts, te))
223
- };
224
-
225
- any | ascii_nonprint | utf8_multibyte {
226
- text = copy(data, ts, te)
227
- emit(:literal, :literal, text)
228
- };
229
- *|;
230
-
231
- # set escapes scanner
232
- # --------------------------------------------------------------------------
233
- set_escape_sequence := |*
234
- # Special case: in sets, octal sequences have higher priority than backrefs
235
- octal_sequence {
236
- emit(:escape, :octal, copy(data, ts-1, te))
237
- fret;
238
- };
239
-
240
- # Scan all other escapes that work in sets with the generic escape scanner
241
- set_escape > (escaped_set_alpha, 2) {
242
- fhold;
243
- fnext character_set;
244
- fcall escape_sequence;
245
- };
246
-
247
- # Treat all remaining escapes - those not supported in sets - as literal.
248
- # (This currently includes \^, \-, \&, \:, although these could potentially
249
- # be meta chars when not escaped, depending on their position in the set.)
250
- (any | utf8_multibyte) > (escaped_set_alpha, 1) {
251
- emit(:escape, :literal, copy(data, ts-1, te))
252
- fret;
253
- };
254
- *|;
255
-
256
-
257
- # escape sequence scanner
258
- # --------------------------------------------------------------------------
259
- escape_sequence := |*
260
- [1-9] . [0-9]* {
261
- text = copy(data, ts-1, te)
262
-
263
- # If not enough groups have been opened, there is a fallback to either an
264
- # octal or literal interpretation for 2+ digit numerical escapes.
265
- digits = text[1..-1]
266
- if digits.size == 1 || digits.to_i <= capturing_group_count
267
- emit(:backref, :number, text)
268
- elsif digits =~ /\A[0-7]{2,}\z/
269
- emit(:escape, :octal, text)
270
- else
271
- emit(:escape, :literal, text[0..1])
272
- emit(:literal, :literal, text[2..-1])
273
- end
274
-
275
- fret;
276
- };
277
-
278
- octal_sequence {
279
- emit(:escape, :octal, copy(data, ts-1, te))
280
- fret;
281
- };
282
-
283
- [8-9] . [0-9] { # special case, emits two tokens
284
- text = copy(data, ts-1, te)
285
- emit(:escape, :literal, text[0, 2])
286
- emit(:literal, :literal, text[2])
287
- fret;
288
- };
289
-
290
- meta_char {
291
- case text = copy(data, ts-1, te)
292
- when '\.'; emit(:escape, :dot, text)
293
- when '\|'; emit(:escape, :alternation, text)
294
- when '\^'; emit(:escape, :bol, text)
295
- when '\$'; emit(:escape, :eol, text)
296
- when '\?'; emit(:escape, :zero_or_one, text)
297
- when '\*'; emit(:escape, :zero_or_more, text)
298
- when '\+'; emit(:escape, :one_or_more, text)
299
- when '\('; emit(:escape, :group_open, text)
300
- when '\)'; emit(:escape, :group_close, text)
301
- when '\{'; emit(:escape, :interval_open, text)
302
- when '\}'; emit(:escape, :interval_close, text)
303
- when '\['; emit(:escape, :set_open, text)
304
- when '\]'; emit(:escape, :set_close, text)
305
- when "\\\\";
306
- emit(:escape, :backslash, text)
307
- end
308
- fret;
309
- };
310
-
311
- escaped_ascii > (escaped_alpha, 7) {
312
- # \b is emitted as backspace only when inside a character set, otherwise
313
- # it is a word boundary anchor. A syntax might "normalize" it if needed.
314
- case text = copy(data, ts-1, te)
315
- when '\a'; emit(:escape, :bell, text)
316
- when '\b'; emit(:escape, :backspace, text)
317
- when '\e'; emit(:escape, :escape, text)
318
- when '\f'; emit(:escape, :form_feed, text)
319
- when '\n'; emit(:escape, :newline, text)
320
- when '\r'; emit(:escape, :carriage, text)
321
- when '\t'; emit(:escape, :tab, text)
322
- when '\v'; emit(:escape, :vertical_tab, text)
323
- end
324
- fret;
325
- };
326
-
327
- codepoint_sequence > (escaped_alpha, 6) $eof(premature_end_error) {
328
- text = copy(data, ts-1, te)
329
- if text[2] == '{'
330
- emit(:escape, :codepoint_list, text)
331
- else
332
- emit(:escape, :codepoint, text)
333
- end
334
- fret;
335
- };
336
-
337
- high_hex_sequence > (escaped_alpha, 5) {
338
- text = copy(data, ts-1, te)
339
- if regexp_encoding == Encoding::BINARY
340
- text.split(/(?=\\)/).each { |part| emit(:escape, :hex, part) }
341
- else
342
- emit(:escape, :utf8_hex, text)
343
- end
344
- fret;
345
- };
346
-
347
- hex_sequence > (escaped_alpha, 5) @eof(premature_end_error) {
348
- emit(:escape, :hex, copy(data, ts-1, te))
349
- fret;
350
- };
351
-
352
- hex_sequence_err @invalid_sequence_error {
353
- fret;
354
- };
355
-
356
- control_sequence >(escaped_alpha, 4) $eof(premature_end_error) {
357
- emit_meta_control_sequence(data, ts, te, :control)
358
- fret;
359
- };
360
-
361
- meta_sequence >(backslashed, 3) $eof(premature_end_error) {
362
- emit_meta_control_sequence(data, ts, te, :meta_sequence)
363
- fret;
364
- };
365
-
366
- char_type_char > (escaped_alpha, 2) {
367
- fhold;
368
- fnext *(in_set? ? fentry(character_set) : fentry(main));
369
- fcall char_type;
370
- };
371
-
372
- property_char > (escaped_alpha, 2) {
373
- fhold;
374
- fnext *(in_set? ? fentry(character_set) : fentry(main));
375
- fcall unicode_property;
376
- };
377
-
378
- (any -- non_literal_escape) | utf8_multibyte > (escaped_alpha, 1) {
379
- emit(:escape, :literal, copy(data, ts-1, te))
380
- fret;
381
- };
382
- *|;
383
-
384
-
385
- # conditional expressions scanner
386
- # --------------------------------------------------------------------------
387
- conditional_expression := |*
388
- group_lookup . ')' {
389
- text = copy(data, ts, te-1)
390
- text =~ /[^0]/ or raise ValidationError.for(:backref, 'condition', 'invalid ref ID')
391
- emit(:conditional, :condition, text)
392
- emit(:conditional, :condition_close, ')')
393
- };
394
-
395
- any {
396
- fhold;
397
- fcall main;
398
- };
399
- *|;
400
-
401
-
402
- # Main scanner
403
- # --------------------------------------------------------------------------
404
- main := |*
405
-
406
- # Meta characters
407
- # ------------------------------------------------------------------------
408
- dot {
409
- emit(:meta, :dot, copy(data, ts, te))
410
- };
411
-
412
- alternation {
413
- if conditional_stack.last == group_depth
414
- emit(:conditional, :separator, copy(data, ts, te))
415
- else
416
- emit(:meta, :alternation, copy(data, ts, te))
417
- end
418
- };
419
-
420
- # Anchors
421
- # ------------------------------------------------------------------------
422
- beginning_of_line {
423
- emit(:anchor, :bol, copy(data, ts, te))
424
- };
425
-
426
- end_of_line {
427
- emit(:anchor, :eol, copy(data, ts, te))
428
- };
429
-
430
- backslash . keep_mark > (backslashed, 4) {
431
- emit(:keep, :mark, copy(data, ts, te))
432
- };
433
-
434
- backslash . anchor_char > (backslashed, 3) {
435
- case text = copy(data, ts, te)
436
- when '\A'; emit(:anchor, :bos, text)
437
- when '\z'; emit(:anchor, :eos, text)
438
- when '\Z'; emit(:anchor, :eos_ob_eol, text)
439
- when '\b'; emit(:anchor, :word_boundary, text)
440
- when '\B'; emit(:anchor, :nonword_boundary, text)
441
- when '\G'; emit(:anchor, :match_start, text)
442
- end
443
- };
444
-
445
- literal_delimiters {
446
- append_literal(data, ts, te)
447
- };
448
-
449
- # Character sets
450
- # ------------------------------------------------------------------------
451
- set_open >set_opened {
452
- emit(:set, :open, copy(data, ts, te))
453
- fcall character_set;
454
- };
455
-
456
-
457
- # Conditional expression
458
- # (?(condition)Y|N) conditional expression
459
- # ------------------------------------------------------------------------
460
- conditional {
461
- text = copy(data, ts, te)
462
-
463
- conditional_stack << group_depth
464
-
465
- emit(:conditional, :open, text[0..-2])
466
- emit(:conditional, :condition_open, '(')
467
- fcall conditional_expression;
468
- };
469
-
470
-
471
- # (?#...) comments: parsed as a single expression, without introducing a
472
- # new nesting level. Comments may not include parentheses, escaped or not.
473
- # special case for close to get the correct closing count.
474
- # ------------------------------------------------------------------------
475
- (group_open . group_comment) @group_closed {
476
- emit(:group, :comment, copy(data, ts, te))
477
- };
478
-
479
- # Expression options:
480
- # (?imxdau-imx) option on/off
481
- # i: ignore case
482
- # m: multi-line (dot(.) match newline)
483
- # x: extended form
484
- # d: default class rules (1.9 compatible)
485
- # a: ASCII class rules (\s, \w, etc.)
486
- # u: Unicode class rules (\s, \w, etc.)
487
- #
488
- # (?imxdau-imx:subexp) option on/off for subexp
489
- # ------------------------------------------------------------------------
490
- (group_open . group_options) >group_opened {
491
- text = copy(data, ts, te)
492
- if text[2..-1] =~ /([^\-mixdau:]|^$)|-.*([dau])/
493
- raise ValidationError.for(:group_option, $1 || "-#{$2}", text)
494
- end
495
- emit_options(text)
496
- };
497
-
498
- # Assertions
499
- # (?=subexp) look-ahead
500
- # (?!subexp) negative look-ahead
501
- # (?<=subexp) look-behind
502
- # (?<!subexp) negative look-behind
503
- # ------------------------------------------------------------------------
504
- (group_open . assertion_type) >group_opened {
505
- case text = copy(data, ts, te)
506
- when '(?='; emit(:assertion, :lookahead, text)
507
- when '(?!'; emit(:assertion, :nlookahead, text)
508
- when '(?<='; emit(:assertion, :lookbehind, text)
509
- when '(?<!'; emit(:assertion, :nlookbehind, text)
510
- end
511
- };
512
-
513
- # Groups
514
- # (?:subexp) passive (non-captured) group
515
- # (?>subexp) atomic group, don't backtrack in subexp.
516
- # (?~subexp) absence group, matches anything that is not subexp
517
- # (?<name>subexp) named group
518
- # (?'name'subexp) named group (single quoted version)
519
- # (subexp) captured group
520
- # ------------------------------------------------------------------------
521
- (group_open . group_type) >group_opened {
522
- case text = copy(data, ts, te)
523
- when '(?:'; emit(:group, :passive, text)
524
- when '(?>'; emit(:group, :atomic, text)
525
- when '(?~'; emit(:group, :absence, text)
526
-
527
- when /^\(\?(?:<>|'')/
528
- raise ValidationError.for(:group, 'named group', 'name is empty')
529
-
530
- when /^\(\?<[^>]+>/
531
- emit(:group, :named_ab, text)
532
-
533
- when /^\(\?'[^']+'/
534
- emit(:group, :named_sq, text)
535
-
536
- end
537
- };
538
-
539
- group_open @group_opened {
540
- self.capturing_group_count = capturing_group_count + 1
541
- text = copy(data, ts, te)
542
- emit(:group, :capture, text)
543
- };
544
-
545
- group_close @group_closed {
546
- if conditional_stack.last == group_depth + 1
547
- conditional_stack.pop
548
- emit(:conditional, :close, ')')
549
- elsif group_depth >= 0
550
- if spacing_stack.length > 1 &&
551
- spacing_stack.last[:depth] == group_depth + 1
552
- spacing_stack.pop
553
- self.free_spacing = spacing_stack.last[:free_spacing]
554
- end
555
-
556
- emit(:group, :close, ')')
557
- else
558
- raise ValidationError.for(:group, 'group', 'unmatched close parenthesis')
559
- end
560
- };
561
-
562
-
563
- # Group backreference, named and numbered
564
- # ------------------------------------------------------------------------
565
- backslash . (group_ref) > (backslashed, 4) {
566
- case text = copy(data, ts, te)
567
- when /^\\k(.)[^0-9\-][^+\-]*['>]$/
568
- emit(:backref, $1 == '<' ? :name_ref_ab : :name_ref_sq, text)
569
- when /^\\k(.)0*[1-9]\d*['>]$/
570
- emit(:backref, $1 == '<' ? :number_ref_ab : :number_ref_sq, text)
571
- when /^\\k(.)-0*[1-9]\d*['>]$/
572
- emit(:backref, $1 == '<' ? :number_rel_ref_ab : :number_rel_ref_sq, text)
573
- when /^\\k(.)[^0-9\-].*[+\-]\d+['>]$/
574
- emit(:backref, $1 == '<' ? :name_recursion_ref_ab : :name_recursion_ref_sq, text)
575
- when /^\\k(.)-?0*[1-9]\d*[+\-]\d+['>]$/
576
- emit(:backref, $1 == '<' ? :number_recursion_ref_ab : :number_recursion_ref_sq, text)
577
- else
578
- raise ValidationError.for(:backref, 'backreference', 'invalid ref ID')
579
- end
580
- };
581
-
582
- # Group call, named and numbered
583
- # ------------------------------------------------------------------------
584
- backslash . (group_call) > (backslashed, 4) {
585
- case text = copy(data, ts, te)
586
- when /^\\g(.)[^0-9+\-].*['>]$/
587
- emit(:backref, $1 == '<' ? :name_call_ab : :name_call_sq, text)
588
- when /^\\g(.)(?:0|0*[1-9]\d*)['>]$/
589
- emit(:backref, $1 == '<' ? :number_call_ab : :number_call_sq, text)
590
- when /^\\g(.)[+-]0*[1-9]\d*/
591
- emit(:backref, $1 == '<' ? :number_rel_call_ab : :number_rel_call_sq, text)
592
- else
593
- raise ValidationError.for(:backref, 'subexpression call', 'invalid ref ID')
594
- end
595
- };
596
-
597
-
598
- # Quantifiers
599
- # ------------------------------------------------------------------------
600
- zero_or_one {
601
- case text = copy(data, ts, te)
602
- when '?' ; emit(:quantifier, :zero_or_one, text)
603
- when '??'; emit(:quantifier, :zero_or_one_reluctant, text)
604
- when '?+'; emit(:quantifier, :zero_or_one_possessive, text)
605
- end
606
- };
607
-
608
- zero_or_more {
609
- case text = copy(data, ts, te)
610
- when '*' ; emit(:quantifier, :zero_or_more, text)
611
- when '*?'; emit(:quantifier, :zero_or_more_reluctant, text)
612
- when '*+'; emit(:quantifier, :zero_or_more_possessive, text)
613
- end
614
- };
615
-
616
- one_or_more {
617
- case text = copy(data, ts, te)
618
- when '+' ; emit(:quantifier, :one_or_more, text)
619
- when '+?'; emit(:quantifier, :one_or_more_reluctant, text)
620
- when '++'; emit(:quantifier, :one_or_more_possessive, text)
621
- end
622
- };
623
-
624
- quantifier_interval {
625
- emit(:quantifier, :interval, copy(data, ts, te))
626
- };
627
-
628
- # Catch unmatched curly braces as literals
629
- range_open {
630
- append_literal(data, ts, te)
631
- };
632
-
633
- # Escaped sequences
634
- # ------------------------------------------------------------------------
635
- backslash > (backslashed, 1) {
636
- fcall escape_sequence;
637
- };
638
-
639
- comment {
640
- if free_spacing
641
- emit(:free_space, :comment, copy(data, ts, te))
642
- else
643
- # consume only the pound sign (#) and backtrack to do regular scanning
644
- append_literal(data, ts, ts + 1)
645
- fexec ts + 1;
646
- end
647
- };
648
-
649
- space+ {
650
- if free_spacing
651
- emit(:free_space, :whitespace, copy(data, ts, te))
652
- else
653
- append_literal(data, ts, te)
654
- end
655
- };
656
-
657
- # Literal: any run of ASCII (pritable or non-printable), and/or UTF-8,
658
- # except meta characters.
659
- # ------------------------------------------------------------------------
660
- (ascii_print -- space)+ | ascii_nonprint+ | utf8_multibyte+ {
661
- append_literal(data, ts, te)
662
- };
663
-
664
- *|;
665
- }%%
666
-
667
- require_relative 'scanner/errors/scanner_error'
668
- require_relative 'scanner/errors/premature_end_error'
669
- require_relative 'scanner/errors/validation_error'
670
-
671
- class Regexp::Scanner
672
- # Scans the given regular expression text, or Regexp object and collects the
673
- # emitted token into an array that gets returned at the end. If a block is
674
- # given, it gets called for each emitted token.
675
- #
676
- # This method may raise errors if a syntax error is encountered.
677
- # --------------------------------------------------------------------------
678
- def self.scan(input_object, options: nil, collect_tokens: true, &block)
679
- new.scan(input_object, options: options, collect_tokens: collect_tokens, &block)
680
- end
681
-
682
- def scan(input_object, options: nil, collect_tokens: true, &block)
683
- self.collect_tokens = collect_tokens
684
- self.literal_run = nil
685
- stack = []
686
-
687
- input = input_object.is_a?(Regexp) ? input_object.source : input_object
688
- self.free_spacing = free_spacing?(input_object, options)
689
- self.regexp_encoding = extract_encoding(input_object, options)
690
- self.spacing_stack = [{:free_spacing => free_spacing, :depth => 0}]
691
-
692
- data = input.unpack("c*")
693
- eof = data.length
694
-
695
- self.tokens = []
696
- self.block = block
697
-
698
- self.set_depth = 0
699
- self.group_depth = 0
700
- self.capturing_group_count = 0
701
- self.conditional_stack = []
702
- self.char_pos = 0
703
-
704
- %% write data;
705
- %% write init;
706
- %% write exec;
707
-
708
- # to avoid "warning: assigned but unused variable - testEof"
709
- testEof = testEof
710
-
711
- if cs == re_scanner_error
712
- text = copy(data, ts ? ts-1 : 0, -1)
713
- raise ScannerError.new("Scan error at '#{text}'")
714
- end
715
-
716
- raise PrematureEndError.new("(missing group closing paranthesis) "+
717
- "[#{group_depth}]") if in_group?
718
- raise PrematureEndError.new("(missing set closing bracket) "+
719
- "[#{set_depth}]") if in_set?
720
-
721
- # when the entire expression is a literal run
722
- emit_literal if literal_run
723
-
724
- tokens
725
- end
726
-
727
- # lazy-load property maps when first needed
728
- def self.short_prop_map
729
- @short_prop_map ||= parse_prop_map('short')
730
- end
731
-
732
- def self.long_prop_map
733
- @long_prop_map ||= parse_prop_map('long')
734
- end
735
-
736
- def self.parse_prop_map(name)
737
- File.read("#{__dir__}/scanner/properties/#{name}.csv").scan(/(.+),(.+)/).to_h
738
- end
739
-
740
- # Use each_with_object for required_ruby_version >= 2.2, or #to_h for >= 2.6
741
- POSIX_CLASSES =
742
- %w[alnum alpha ascii blank cntrl digit graph
743
- lower print punct space upper word xdigit]
744
- .inject({}) { |o, e| o.merge(e => true) }.freeze
745
-
746
- # Emits an array with the details of the scanned pattern
747
- def emit(type, token, text)
748
- #puts "EMIT: type: #{type}, token: #{token}, text: #{text}, ts: #{ts}, te: #{te}"
749
-
750
- emit_literal if literal_run
751
-
752
- # Ragel runs with byte-based indices (ts, te). These are of little value to
753
- # end-users, so we keep track of char-based indices and emit those instead.
754
- ts_char_pos = char_pos
755
- te_char_pos = char_pos + text.length
756
-
757
- tok = [type, token, text, ts_char_pos, te_char_pos]
758
-
759
- self.prev_token = tok
760
-
761
- self.char_pos = te_char_pos
762
-
763
- if block
764
- block.call type, token, text, ts_char_pos, te_char_pos
765
- # TODO: in v3.0.0, remove `collect_tokens:` kwarg and only collect if no block given
766
- tokens << tok if collect_tokens
767
- elsif collect_tokens
768
- tokens << tok
769
- end
770
- end
771
-
772
- attr_accessor :capturing_group_count, :literal_run # only public for #||= to work on ruby <= 2.5
773
-
774
- private
775
-
776
- attr_accessor :block,
777
- :collect_tokens, :tokens, :prev_token,
778
- :free_spacing, :spacing_stack,
779
- :regexp_encoding,
780
- :group_depth, :set_depth, :conditional_stack,
781
- :char_pos
782
-
783
- def extract_encoding(input_object, options)
784
- if input_object.is_a?(::Regexp)
785
- input_object.encoding
786
- elsif options && (options & Regexp::NOENCODING)
787
- Encoding::BINARY
788
- end
789
- end
790
-
791
- def free_spacing?(input_object, options)
792
- if options && !input_object.is_a?(String)
793
- raise ArgumentError, 'options cannot be supplied unless scanning a String'
794
- end
795
-
796
- options = input_object.options if input_object.is_a?(::Regexp)
797
-
798
- return false unless options
799
-
800
- options & Regexp::EXTENDED != 0
801
- end
802
-
803
- def in_group?
804
- group_depth > 0
805
- end
806
-
807
- def in_set?
808
- set_depth > 0
809
- end
810
-
811
- # Copy from ts to te from data as text
812
- def copy(data, ts, te)
813
- data[ts...te].pack('c*').force_encoding('utf-8')
814
- end
815
-
816
- # Appends one or more characters to the literal buffer, to be emitted later
817
- # by a call to emit_literal.
818
- def append_literal(data, ts, te)
819
- (self.literal_run ||= []) << copy(data, ts, te)
820
- end
821
-
822
- # Emits the literal run collected by calls to the append_literal method.
823
- def emit_literal
824
- text = literal_run.join
825
- self.literal_run = nil
826
- emit(:literal, :literal, text)
827
- end
828
-
829
- def emit_options(text)
830
- token = nil
831
-
832
- # Ruby allows things like '(?-xxxx)' or '(?xx-xx--xx-:abc)'.
833
- text =~ /\(\?([mixdau]*)(-(?:[mix]*))*(:)?/
834
- positive, negative, group_local = $1, $2, $3
835
-
836
- if positive.include?('x')
837
- self.free_spacing = true
838
- end
839
-
840
- # If the x appears in both, treat it like ruby does, the second cancels
841
- # the first.
842
- if negative && negative.include?('x')
843
- self.free_spacing = false
844
- end
845
-
846
- if group_local
847
- spacing_stack << {:free_spacing => free_spacing, :depth => group_depth}
848
- token = :options
849
- else
850
- # switch for parent group level
851
- spacing_stack.last[:free_spacing] = free_spacing
852
- token = :options_switch
853
- end
854
-
855
- emit(:group, token, text)
856
- end
857
-
858
- def emit_meta_control_sequence(data, ts, te, token)
859
- if data.last < 0x00 || data.last > 0x7F
860
- raise ValidationError.for(:sequence, 'escape', token.to_s)
861
- end
862
- emit(:escape, token, copy(data, ts-1, te))
863
- end
864
- end # module Regexp::Scanner