regexp_parser 1.6.0 → 1.8.2
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 +4 -4
- data/CHANGELOG.md +57 -0
- data/Gemfile +1 -1
- data/README.md +24 -12
- data/lib/regexp_parser/expression.rb +6 -2
- data/lib/regexp_parser/expression/methods/match_length.rb +1 -1
- data/lib/regexp_parser/expression/methods/traverse.rb +3 -1
- data/lib/regexp_parser/lexer.rb +4 -4
- data/lib/regexp_parser/parser.rb +18 -12
- data/lib/regexp_parser/scanner.rb +1023 -992
- data/lib/regexp_parser/scanner/scanner.rl +42 -18
- data/lib/regexp_parser/syntax/version_lookup.rb +2 -2
- data/lib/regexp_parser/version.rb +1 -1
- data/regexp_parser.gemspec +1 -1
- data/spec/expression/methods/match_length_spec.rb +7 -0
- data/spec/expression/methods/traverse_spec.rb +21 -0
- data/spec/expression/options_spec.rb +33 -33
- data/spec/lexer/delimiters_spec.rb +68 -0
- data/spec/parser/options_spec.rb +28 -0
- data/spec/parser/quantifiers_spec.rb +1 -0
- data/spec/scanner/delimiters_spec.rb +52 -0
- data/spec/scanner/errors_spec.rb +0 -1
- data/spec/scanner/escapes_spec.rb +4 -0
- data/spec/scanner/free_space_spec.rb +32 -0
- data/spec/scanner/options_spec.rb +36 -0
- metadata +67 -59
@@ -21,7 +21,7 @@
|
|
21
21
|
set_close = ']';
|
22
22
|
brackets = set_open | set_close;
|
23
23
|
|
24
|
-
comment = ('#' . [^\n]* . '\n');
|
24
|
+
comment = ('#' . [^\n]* . '\n'?);
|
25
25
|
|
26
26
|
class_name_posix = 'alnum' | 'alpha' | 'blank' |
|
27
27
|
'cntrl' | 'digit' | 'graph' |
|
@@ -62,13 +62,17 @@
|
|
62
62
|
quantifier_possessive = '?+' | '*+' | '++';
|
63
63
|
quantifier_mode = '?' | '+';
|
64
64
|
|
65
|
-
|
66
|
-
|
65
|
+
quantity_exact = (digit+);
|
66
|
+
quantity_minimum = (digit+) . ',';
|
67
|
+
quantity_maximum = ',' . (digit+);
|
68
|
+
quantity_range = (digit+) . ',' . (digit+);
|
69
|
+
quantifier_interval = range_open . ( quantity_exact | quantity_minimum |
|
70
|
+
quantity_maximum | quantity_range ) . range_close .
|
71
|
+
quantifier_mode?;
|
67
72
|
|
68
73
|
quantifiers = quantifier_greedy | quantifier_reluctant |
|
69
74
|
quantifier_possessive | quantifier_interval;
|
70
75
|
|
71
|
-
|
72
76
|
conditional = '(?(';
|
73
77
|
|
74
78
|
group_comment = '?#' . [^)]* . group_close;
|
@@ -114,7 +118,9 @@
|
|
114
118
|
curlies | parantheses | brackets |
|
115
119
|
line_anchor | quantifier_greedy;
|
116
120
|
|
117
|
-
|
121
|
+
literal_delimiters = ']' | '}';
|
122
|
+
|
123
|
+
ascii_print = ((0x20..0x7e) - meta_char - '#');
|
118
124
|
ascii_nonprint = (0x01..0x1f | 0x7f);
|
119
125
|
|
120
126
|
utf8_2_byte = (0xc2..0xdf 0x80..0xbf);
|
@@ -122,7 +128,7 @@
|
|
122
128
|
utf8_4_byte = (0xf0..0xf4 0x80..0xbf 0x80..0xbf 0x80..0xbf);
|
123
129
|
|
124
130
|
non_literal_escape = char_type_char | anchor_char | escaped_ascii |
|
125
|
-
|
131
|
+
keep_mark | [xucCM];
|
126
132
|
|
127
133
|
non_set_escape = (anchor_char - 'b') | group_ref | keep_mark |
|
128
134
|
multi_codepoint_char_type | [0-9cCM];
|
@@ -417,6 +423,10 @@
|
|
417
423
|
end
|
418
424
|
};
|
419
425
|
|
426
|
+
literal_delimiters {
|
427
|
+
append_literal(data, ts, te)
|
428
|
+
};
|
429
|
+
|
420
430
|
# Character sets
|
421
431
|
# ------------------------------------------------------------------------
|
422
432
|
set_open >set_opened {
|
@@ -620,10 +630,15 @@
|
|
620
630
|
end
|
621
631
|
};
|
622
632
|
|
623
|
-
quantifier_interval
|
633
|
+
quantifier_interval {
|
624
634
|
emit(:quantifier, :interval, *text(data, ts, te))
|
625
635
|
};
|
626
636
|
|
637
|
+
# Catch unmatched curly braces as literals
|
638
|
+
range_open {
|
639
|
+
append_literal(data, ts, te)
|
640
|
+
};
|
641
|
+
|
627
642
|
# Escaped sequences
|
628
643
|
# ------------------------------------------------------------------------
|
629
644
|
backslash > (backslashed, 1) {
|
@@ -634,7 +649,9 @@
|
|
634
649
|
if free_spacing
|
635
650
|
emit(:free_space, :comment, *text(data, ts, te))
|
636
651
|
else
|
637
|
-
|
652
|
+
# consume only the pound sign (#) and backtrack to do regular scanning
|
653
|
+
append_literal(data, ts, ts + 1)
|
654
|
+
fexec ts + 1;
|
638
655
|
end
|
639
656
|
};
|
640
657
|
|
@@ -722,21 +739,16 @@ class Regexp::Scanner
|
|
722
739
|
#
|
723
740
|
# This method may raise errors if a syntax error is encountered.
|
724
741
|
# --------------------------------------------------------------------------
|
725
|
-
def self.scan(input_object, &block)
|
726
|
-
new.scan(input_object, &block)
|
742
|
+
def self.scan(input_object, options: nil, &block)
|
743
|
+
new.scan(input_object, options: options, &block)
|
727
744
|
end
|
728
745
|
|
729
|
-
def scan(input_object, &block)
|
746
|
+
def scan(input_object, options: nil, &block)
|
730
747
|
self.literal = nil
|
731
748
|
stack = []
|
732
749
|
|
733
|
-
|
734
|
-
|
735
|
-
self.free_spacing = (input_object.options & Regexp::EXTENDED != 0)
|
736
|
-
else
|
737
|
-
input = input_object
|
738
|
-
self.free_spacing = false
|
739
|
-
end
|
750
|
+
input = input_object.is_a?(Regexp) ? input_object.source : input_object
|
751
|
+
self.free_spacing = free_spacing?(input_object, options)
|
740
752
|
self.spacing_stack = [{:free_spacing => free_spacing, :depth => 0}]
|
741
753
|
|
742
754
|
data = input.unpack("c*") if input.is_a?(String)
|
@@ -802,6 +814,18 @@ class Regexp::Scanner
|
|
802
814
|
attr_accessor :tokens, :literal, :block, :free_spacing, :spacing_stack,
|
803
815
|
:group_depth, :set_depth, :conditional_stack
|
804
816
|
|
817
|
+
def free_spacing?(input_object, options)
|
818
|
+
if options && !input_object.is_a?(String)
|
819
|
+
raise ArgumentError, 'options cannot be supplied unless scanning a String'
|
820
|
+
end
|
821
|
+
|
822
|
+
options = input_object.options if input_object.is_a?(::Regexp)
|
823
|
+
|
824
|
+
return false unless options
|
825
|
+
|
826
|
+
options & Regexp::EXTENDED != 0
|
827
|
+
end
|
828
|
+
|
805
829
|
def in_group?
|
806
830
|
group_depth > 0
|
807
831
|
end
|
@@ -74,9 +74,9 @@ module Regexp::Syntax
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def warn_if_future_version(const_name)
|
77
|
-
return if comparable_version(const_name) < comparable_version('
|
77
|
+
return if comparable_version(const_name) < comparable_version('4.0.0')
|
78
78
|
|
79
|
-
warn('This library has only been tested up to Ruby
|
79
|
+
warn('This library has only been tested up to Ruby 3.x, '\
|
80
80
|
"but you are running with #{const_get(const_name).inspect}")
|
81
81
|
end
|
82
82
|
end
|
data/regexp_parser.gemspec
CHANGED
@@ -120,6 +120,13 @@ RSpec.describe(Regexp::MatchLength) do
|
|
120
120
|
expect { result.next }.to raise_error(StopIteration)
|
121
121
|
end
|
122
122
|
|
123
|
+
it 'is aware of limit option even if called without a block' do
|
124
|
+
result = ML.of(/a?/).each(limit: 1)
|
125
|
+
expect(result).to be_a(Enumerator)
|
126
|
+
expect(result.next).to eq 0
|
127
|
+
expect { result.next }.to raise_error(StopIteration)
|
128
|
+
end
|
129
|
+
|
123
130
|
it 'is limited to 1000 iterations in case there are infinite match lengths' do
|
124
131
|
expect(ML.of(/a*/).first(3000).size).to eq 1000
|
125
132
|
end
|
@@ -39,6 +39,17 @@ RSpec.describe('Subexpression#traverse') do
|
|
39
39
|
expect(visits).to eq 9
|
40
40
|
end
|
41
41
|
|
42
|
+
specify('Subexpression#traverse without a block') do
|
43
|
+
root = RP.parse(/abc/)
|
44
|
+
enum = root.traverse
|
45
|
+
|
46
|
+
expect(enum).to be_a(Enumerator)
|
47
|
+
event, expr, idx = enum.next
|
48
|
+
expect(event).to eq(:visit)
|
49
|
+
expect(expr).to be_a(Regexp::Expression::Literal)
|
50
|
+
expect(idx).to eq(0)
|
51
|
+
end
|
52
|
+
|
42
53
|
specify('Subexpression#walk alias') do
|
43
54
|
root = RP.parse(/abc/)
|
44
55
|
|
@@ -81,6 +92,16 @@ RSpec.describe('Subexpression#traverse') do
|
|
81
92
|
expect(indices).to eq [0, 0, 1, 0, 2]
|
82
93
|
end
|
83
94
|
|
95
|
+
specify('Subexpression#each_expression without a block') do
|
96
|
+
root = RP.parse(/abc/)
|
97
|
+
enum = root.each_expression
|
98
|
+
|
99
|
+
expect(enum).to be_a(Enumerator)
|
100
|
+
expr, idx = enum.next
|
101
|
+
expect(expr).to be_a(Regexp::Expression::Literal)
|
102
|
+
expect(idx).to eq(0)
|
103
|
+
end
|
104
|
+
|
84
105
|
specify('Subexpression#flat_map without block') do
|
85
106
|
root = RP.parse(/a(b([c-e]+))?/)
|
86
107
|
|
@@ -85,44 +85,44 @@ RSpec.describe('Expression#options') do
|
|
85
85
|
.and change { exp.unicode_classes? }.from(false).to(true)
|
86
86
|
end
|
87
87
|
|
88
|
-
RSpec.shared_examples '#options' do |regexp,
|
88
|
+
RSpec.shared_examples '#options' do |regexp, path, klass|
|
89
89
|
it "works for expression class #{klass}" do
|
90
|
-
exp = RP.parse(/#{regexp.source}/i).dig(*
|
90
|
+
exp = RP.parse(/#{regexp.source}/i).dig(*path)
|
91
91
|
expect(exp).to be_a(klass)
|
92
92
|
expect(exp).to be_i
|
93
93
|
expect(exp).not_to be_x
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
include_examples '#options', //, Root
|
98
|
-
include_examples '#options', /a/,
|
99
|
-
include_examples '#options', /\A/, Anchor::Base
|
100
|
-
include_examples '#options', /\d/, CharacterType::Base
|
101
|
-
include_examples '#options', /\n/, EscapeSequence::Base
|
102
|
-
include_examples '#options', /\K/, Keep::Mark
|
103
|
-
include_examples '#options', /./, CharacterType::Any
|
104
|
-
include_examples '#options', /(a)/, Group::Base
|
105
|
-
include_examples '#options', /(a)/,
|
106
|
-
include_examples '#options', /(?=a)/, Assertion::Base
|
107
|
-
include_examples '#options', /(?=a)/,
|
108
|
-
include_examples '#options', /(a|b)/, Group::Base
|
109
|
-
include_examples '#options', /(a|b)/,
|
110
|
-
include_examples '#options', /(a|b)/,
|
111
|
-
include_examples '#options', /(a|b)/,
|
112
|
-
include_examples '#options', /(a)\1/, Backreference::Base
|
113
|
-
include_examples '#options', /(a)\k<1>/, Backreference::Number
|
114
|
-
include_examples '#options', /(a)\g<1>/, Backreference::NumberCall
|
115
|
-
include_examples '#options', /[a]/,
|
116
|
-
include_examples '#options', /[a]/,
|
117
|
-
include_examples '#options', /[a-z]/,
|
118
|
-
include_examples '#options', /[a-z]/,
|
119
|
-
include_examples '#options', /[a&&z]/,
|
120
|
-
include_examples '#options', /[a&&z]/,
|
121
|
-
include_examples '#options', /[a&&z]/,
|
122
|
-
include_examples '#options', /[[:ascii:]]/,
|
123
|
-
include_examples '#options', /\p{word}/, UnicodeProperty::Base
|
124
|
-
include_examples '#options', /(a)(?(1)b|c)/, Conditional::Expression
|
125
|
-
include_examples '#options', /(a)(?(1)b|c)/,
|
126
|
-
include_examples '#options', /(a)(?(1)b|c)/,
|
127
|
-
include_examples '#options', /(a)(?(1)b|c)/,
|
97
|
+
include_examples '#options', //, [], Root
|
98
|
+
include_examples '#options', /a/, [0], Literal
|
99
|
+
include_examples '#options', /\A/, [0], Anchor::Base
|
100
|
+
include_examples '#options', /\d/, [0], CharacterType::Base
|
101
|
+
include_examples '#options', /\n/, [0], EscapeSequence::Base
|
102
|
+
include_examples '#options', /\K/, [0], Keep::Mark
|
103
|
+
include_examples '#options', /./, [0], CharacterType::Any
|
104
|
+
include_examples '#options', /(a)/, [0], Group::Base
|
105
|
+
include_examples '#options', /(a)/, [0, 0], Literal
|
106
|
+
include_examples '#options', /(?=a)/, [0], Assertion::Base
|
107
|
+
include_examples '#options', /(?=a)/, [0, 0], Literal
|
108
|
+
include_examples '#options', /(a|b)/, [0], Group::Base
|
109
|
+
include_examples '#options', /(a|b)/, [0, 0], Alternation
|
110
|
+
include_examples '#options', /(a|b)/, [0, 0, 0], Alternative
|
111
|
+
include_examples '#options', /(a|b)/, [0, 0, 0, 0], Literal
|
112
|
+
include_examples '#options', /(a)\1/, [1], Backreference::Base
|
113
|
+
include_examples '#options', /(a)\k<1>/, [1], Backreference::Number
|
114
|
+
include_examples '#options', /(a)\g<1>/, [1], Backreference::NumberCall
|
115
|
+
include_examples '#options', /[a]/, [0], CharacterSet
|
116
|
+
include_examples '#options', /[a]/, [0, 0], Literal
|
117
|
+
include_examples '#options', /[a-z]/, [0, 0], CharacterSet::Range
|
118
|
+
include_examples '#options', /[a-z]/, [0, 0, 0], Literal
|
119
|
+
include_examples '#options', /[a&&z]/, [0, 0], CharacterSet::Intersection
|
120
|
+
include_examples '#options', /[a&&z]/, [0, 0, 0], CharacterSet::IntersectedSequence
|
121
|
+
include_examples '#options', /[a&&z]/, [0, 0, 0, 0], Literal
|
122
|
+
include_examples '#options', /[[:ascii:]]/, [0, 0], PosixClass
|
123
|
+
include_examples '#options', /\p{word}/, [0], UnicodeProperty::Base
|
124
|
+
include_examples '#options', /(a)(?(1)b|c)/, [1], Conditional::Expression
|
125
|
+
include_examples '#options', /(a)(?(1)b|c)/, [1, 0], Conditional::Condition
|
126
|
+
include_examples '#options', /(a)(?(1)b|c)/, [1, 1], Conditional::Branch
|
127
|
+
include_examples '#options', /(a)(?(1)b|c)/, [1, 1, 0], Literal
|
128
128
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe('Literal delimiter lexing') do
|
4
|
+
include_examples 'lex', '}',
|
5
|
+
0 => [:literal, :literal, '}', 0, 1, 0, 0, 0]
|
6
|
+
|
7
|
+
include_examples 'lex', '}}',
|
8
|
+
0 => [:literal, :literal, '}}', 0, 2, 0, 0, 0]
|
9
|
+
|
10
|
+
include_examples 'lex', '{',
|
11
|
+
0 => [:literal, :literal, '{', 0, 1, 0, 0, 0]
|
12
|
+
|
13
|
+
include_examples 'lex', '{{',
|
14
|
+
0 => [:literal, :literal, '{{', 0, 2, 0, 0, 0]
|
15
|
+
|
16
|
+
include_examples 'lex', '{}',
|
17
|
+
0 => [:literal, :literal, '{}', 0, 2, 0, 0, 0]
|
18
|
+
|
19
|
+
include_examples 'lex', '}{',
|
20
|
+
0 => [:literal, :literal, '}{', 0, 2, 0, 0, 0]
|
21
|
+
|
22
|
+
include_examples 'lex', '}{+',
|
23
|
+
0 => [:literal, :literal, '}', 0, 1, 0, 0, 0],
|
24
|
+
1 => [:literal, :literal, '{', 1, 2, 0, 0, 0],
|
25
|
+
2 => [:quantifier, :one_or_more, '+', 2, 3, 0, 0, 0]
|
26
|
+
|
27
|
+
include_examples 'lex', '{{var}}',
|
28
|
+
0 => [:literal, :literal, '{{var}}', 0, 7, 0, 0, 0]
|
29
|
+
|
30
|
+
include_examples 'lex', 'a{b}c',
|
31
|
+
0 => [:literal, :literal, 'a{b}c', 0, 5, 0, 0, 0]
|
32
|
+
|
33
|
+
include_examples 'lex', 'a{1,2',
|
34
|
+
0 => [:literal, :literal, 'a{1,2', 0, 5, 0, 0, 0]
|
35
|
+
|
36
|
+
include_examples 'lex', '({.+})',
|
37
|
+
0 => [:group, :capture, '(', 0, 1, 0, 0, 0],
|
38
|
+
1 => [:literal, :literal, '{', 1, 2, 1, 0, 0],
|
39
|
+
2 => [:meta, :dot, '.', 2, 3, 1, 0, 0],
|
40
|
+
3 => [:quantifier, :one_or_more, '+', 3, 4, 1, 0, 0],
|
41
|
+
4 => [:literal, :literal, '}', 4, 5, 1, 0, 0],
|
42
|
+
5 => [:group, :close, ')', 5, 6, 0, 0, 0]
|
43
|
+
|
44
|
+
include_examples 'lex', ']',
|
45
|
+
0 => [:literal, :literal, ']', 0, 1, 0, 0, 0]
|
46
|
+
|
47
|
+
include_examples 'lex', ']]',
|
48
|
+
0 => [:literal, :literal, ']]', 0, 2, 0, 0, 0]
|
49
|
+
|
50
|
+
include_examples 'lex', ']\[',
|
51
|
+
0 => [:literal, :literal, ']', 0, 1, 0, 0, 0],
|
52
|
+
1 => [:escape, :set_open, '\[', 1, 3, 0, 0, 0]
|
53
|
+
|
54
|
+
include_examples 'lex', '()',
|
55
|
+
0 => [:group, :capture, '(', 0, 1, 0, 0, 0],
|
56
|
+
1 => [:group, :close, ')', 1, 2, 0, 0, 0]
|
57
|
+
|
58
|
+
include_examples 'lex', '{abc:.+}}}[^}]]}',
|
59
|
+
0 => [:literal, :literal, '{abc:', 0, 5, 0, 0, 0],
|
60
|
+
1 => [:meta, :dot, '.', 5, 6, 0, 0, 0],
|
61
|
+
2 => [:quantifier, :one_or_more, '+', 6, 7, 0, 0, 0],
|
62
|
+
3 => [:literal, :literal, '}}}', 7, 10, 0, 0, 0],
|
63
|
+
4 => [:set, :open, '[', 10, 11, 0, 0, 0],
|
64
|
+
5 => [:set, :negate, '^', 11, 12, 0, 1, 0],
|
65
|
+
6 => [:literal, :literal, '}', 12, 13, 0, 1, 0],
|
66
|
+
7 => [:set, :close, ']', 13, 14, 0, 0, 0],
|
67
|
+
8 => [:literal, :literal, ']}', 14, 16, 0, 0, 0]
|
68
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe('passing options to parse') do
|
4
|
+
it 'raises if if parsing from a Regexp and options are passed' do
|
5
|
+
expect { RP.parse(/a+/, options: ::Regexp::EXTENDED) }.to raise_error(
|
6
|
+
ArgumentError,
|
7
|
+
'options cannot be supplied unless parsing a String'
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'sets options if parsing from a String' do
|
12
|
+
root = RP.parse('a+', options: ::Regexp::MULTILINE | ::Regexp::EXTENDED)
|
13
|
+
|
14
|
+
expect(root.options).to eq(m: true, x: true)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'allows options to not be supplied when parsing from a Regexp' do
|
18
|
+
root = RP.parse(/a+/ix)
|
19
|
+
|
20
|
+
expect(root.options).to eq(i: true, x: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has an empty option-hash when parsing from a String and passing no options' do
|
24
|
+
root = RP.parse('a+')
|
25
|
+
|
26
|
+
expect(root.options).to be_empty
|
27
|
+
end
|
28
|
+
end
|
@@ -35,6 +35,7 @@ RSpec.describe('Quantifier parsing') do
|
|
35
35
|
include_examples 'quantifier', /a{4}b/, '{4}', :greedy, :interval, 4, 4
|
36
36
|
include_examples 'quantifier', /a{4}?b/, '{4}?', :reluctant, :interval, 4, 4
|
37
37
|
include_examples 'quantifier', /a{4}+b/, '{4}+', :possessive, :interval, 4, 4
|
38
|
+
include_examples 'quantifier', /a{004}+b/, '{004}+', :possessive, :interval, 4, 4
|
38
39
|
|
39
40
|
specify('mode-checking methods') do
|
40
41
|
exp = RP.parse(/a??/).first
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe('Literal delimiter scanning') do
|
4
|
+
include_examples 'scan', '}',
|
5
|
+
0 => [:literal, :literal, '}', 0, 1]
|
6
|
+
|
7
|
+
include_examples 'scan', '}}',
|
8
|
+
0 => [:literal, :literal, '}}', 0, 2]
|
9
|
+
|
10
|
+
include_examples 'scan', '{',
|
11
|
+
0 => [:literal, :literal, '{', 0, 1]
|
12
|
+
|
13
|
+
include_examples 'scan', '{{',
|
14
|
+
0 => [:literal, :literal, '{{', 0, 2]
|
15
|
+
|
16
|
+
include_examples 'scan', '{}',
|
17
|
+
0 => [:literal, :literal, '{}', 0, 2]
|
18
|
+
|
19
|
+
include_examples 'scan', '}{',
|
20
|
+
0 => [:literal, :literal, '}{', 0, 2]
|
21
|
+
|
22
|
+
include_examples 'scan', '}{+',
|
23
|
+
0 => [:literal, :literal, '}{', 0, 2]
|
24
|
+
|
25
|
+
include_examples 'scan', '{{var}}',
|
26
|
+
0 => [:literal, :literal, '{{var}}', 0, 7]
|
27
|
+
|
28
|
+
include_examples 'scan', 'a{1,2',
|
29
|
+
0 => [:literal, :literal, 'a{1,2', 0, 5]
|
30
|
+
|
31
|
+
include_examples 'scan', '({.+})',
|
32
|
+
0 => [:group, :capture, '(', 0, 1],
|
33
|
+
1 => [:literal, :literal, '{', 1, 2],
|
34
|
+
2 => [:meta, :dot, '.', 2, 3],
|
35
|
+
3 => [:quantifier, :one_or_more, '+', 3, 4],
|
36
|
+
4 => [:literal, :literal, '}', 4, 5],
|
37
|
+
5 => [:group, :close, ')', 5, 6]
|
38
|
+
|
39
|
+
include_examples 'scan', ']',
|
40
|
+
0 => [:literal, :literal, ']', 0, 1]
|
41
|
+
|
42
|
+
include_examples 'scan', ']]',
|
43
|
+
0 => [:literal, :literal, ']]', 0, 2]
|
44
|
+
|
45
|
+
include_examples 'scan', ']\[',
|
46
|
+
0 => [:literal, :literal, ']', 0, 1],
|
47
|
+
1 => [:escape, :set_open, '\[', 1, 3]
|
48
|
+
|
49
|
+
include_examples 'scan', '()',
|
50
|
+
0 => [:group, :capture, '(', 0, 1],
|
51
|
+
1 => [:group, :close, ')', 1, 2]
|
52
|
+
end
|