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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/lib/regexp_parser/expression/base.rb +50 -14
- data/lib/regexp_parser/expression/classes/character_set/range.rb +1 -1
- data/lib/regexp_parser/expression/classes/character_set.rb +1 -1
- data/lib/regexp_parser/expression/classes/unicode_property.rb +1 -1
- data/lib/regexp_parser/expression/methods/match_length.rb +15 -7
- data/lib/regexp_parser/expression/methods/tests.rb +20 -4
- data/lib/regexp_parser/expression/methods/traverse.rb +51 -13
- data/lib/regexp_parser/expression/quantifier.rb +1 -1
- data/lib/regexp_parser/expression/shared.rb +24 -22
- data/lib/regexp_parser/expression/subexpression.rb +2 -2
- data/lib/regexp_parser/lexer.rb +21 -22
- data/lib/regexp_parser/parser.rb +12 -36
- data/lib/regexp_parser/scanner.rb +184 -236
- data/lib/regexp_parser/syntax/version_lookup.rb +25 -10
- data/lib/regexp_parser/version.rb +1 -1
- data/regexp_parser.gemspec +3 -9
- metadata +2 -9
- data/Gemfile +0 -17
- data/Rakefile +0 -25
- data/lib/regexp_parser/scanner/char_type.rl +0 -28
- data/lib/regexp_parser/scanner/property.rl +0 -30
- data/lib/regexp_parser/scanner/scanner.rl +0 -864
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3064d283e8ab829bdaffd9312b9e975ff3c47ace80c2add1dd9336c4cefb955b
|
|
4
|
+
data.tar.gz: 78c30fac8b319e64ba6cadf096b03e88f65fd87171f706bb4ad6f1d2abbcd9c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3d1a370ef4671e0dfb50e30612af9364e26f82b0facdc3e526dcf403c06e9fefa52e31f68f7d1949ca28d213603a46af2385ff8831822494b29cc9d945b2f90
|
|
7
|
+
data.tar.gz: b81338ab447b5f03156770c6ba7f165dcc18f4d0ff0c9c3e00a64baedae62da927d508e1fb68c7893eb19738bf6db9edec204bc248b89a92af0f81a60d31df16
|
data/LICENSE
CHANGED
|
@@ -26,24 +26,17 @@ module Regexp::Expression
|
|
|
26
26
|
|
|
27
27
|
# Deprecated. Prefer `#repetitions` which has a more uniform interface.
|
|
28
28
|
def quantity
|
|
29
|
-
return [nil,nil] unless quantified?
|
|
29
|
+
return [nil, nil] unless quantified?
|
|
30
|
+
|
|
30
31
|
[quantifier.min, quantifier.max]
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def repetitions
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# fix Range#minmax on old Rubies - https://bugs.ruby-lang.org/issues/15807
|
|
40
|
-
if RUBY_VERSION.to_f < 2.7
|
|
41
|
-
range.define_singleton_method(:minmax) { [min, max] }
|
|
42
|
-
end
|
|
43
|
-
range
|
|
44
|
-
else
|
|
45
|
-
1..1
|
|
46
|
-
end
|
|
35
|
+
return 1..1 unless quantified?
|
|
36
|
+
|
|
37
|
+
min = quantifier.min
|
|
38
|
+
max = quantifier.max < 0 ? Float::INFINITY : quantifier.max
|
|
39
|
+
min..max
|
|
47
40
|
end
|
|
48
41
|
|
|
49
42
|
def greedy?
|
|
@@ -74,5 +67,48 @@ module Regexp::Expression
|
|
|
74
67
|
}
|
|
75
68
|
end
|
|
76
69
|
alias :attributes :to_h
|
|
70
|
+
|
|
71
|
+
# Recalculates the nesting_level for this node and children, which reflects
|
|
72
|
+
# how deep a node is in the tree. Do this at the end of parsing to account
|
|
73
|
+
# for tree rewrites.
|
|
74
|
+
# This must be safe against overflows and performant - check the benchmarks.
|
|
75
|
+
def recursively_update_nesting_levels(base = nesting_level)
|
|
76
|
+
queue = [base, self]
|
|
77
|
+
|
|
78
|
+
until queue.empty?
|
|
79
|
+
exp = queue.pop
|
|
80
|
+
exp_lvl = queue.pop
|
|
81
|
+
exp.nesting_level = exp_lvl
|
|
82
|
+
exp.quantifier.nesting_level = exp_lvl if exp.quantifier
|
|
83
|
+
exp.terminal? || exp.expressions.each do |subexp|
|
|
84
|
+
queue.push(exp_lvl + 1, subexp)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Assigns referenced expressions to referring expressions, e.g. if there is
|
|
90
|
+
# an instance of Backreference::Number, its #referenced_expression is set to
|
|
91
|
+
# the instance of Group::Capture that it refers to via its number.
|
|
92
|
+
def assign_referenced_expressions
|
|
93
|
+
# find all referenceable and referring expressions
|
|
94
|
+
targets = {}
|
|
95
|
+
targets[0] = [self] if instance_of?(Root)
|
|
96
|
+
referrers = []
|
|
97
|
+
each_expression do |exp|
|
|
98
|
+
if exp.referential?
|
|
99
|
+
referrers << exp
|
|
100
|
+
elsif exp.is_a?(Group::Capture)
|
|
101
|
+
(targets[exp.identifier] ||= []) << exp
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
# assign referenced expressions to referring expressions
|
|
105
|
+
# (in a second iteration because there might be forward references)
|
|
106
|
+
referrers.each do |exp|
|
|
107
|
+
exp.referenced_expressions = targets[exp.reference] || raise(
|
|
108
|
+
Regexp::Parser::ParserError,
|
|
109
|
+
"Invalid reference #{exp.reference} at pos #{exp.ts}"
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
77
113
|
end
|
|
78
114
|
end
|
|
@@ -112,9 +112,9 @@ module Regexp::Expression
|
|
|
112
112
|
class Subexpression
|
|
113
113
|
def match_length
|
|
114
114
|
MatchLength.new(self,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
base_min: map { |exp| exp.match_length.min }.inject(0, :+),
|
|
116
|
+
base_max: map { |exp| exp.match_length.max }.inject(0, :+),
|
|
117
|
+
reify: ->{ map { |exp| exp.match_length.to_re }.join })
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def inner_match_length
|
|
@@ -132,9 +132,9 @@ module Regexp::Expression
|
|
|
132
132
|
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
133
133
|
def match_length
|
|
134
134
|
MatchLength.new(self,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
base_min: map { |exp| exp.match_length.min }.min,
|
|
136
|
+
base_max: map { |exp| exp.match_length.max }.max,
|
|
137
|
+
reify: ->{ map { |exp| exp.match_length.to_re }.join('|') })
|
|
138
138
|
end
|
|
139
139
|
RUBY
|
|
140
140
|
end
|
|
@@ -158,7 +158,15 @@ module Regexp::Expression
|
|
|
158
158
|
if referenced_expression.nil?
|
|
159
159
|
raise ArgumentError, 'Missing referenced_expression - not parsed?'
|
|
160
160
|
end
|
|
161
|
-
|
|
161
|
+
# The targets' own quantifiers do not apply: a backref/subexp match what
|
|
162
|
+
# the target captures in one repetition. The backref/call's own quantifier
|
|
163
|
+
# does apply, and they can be multiplexed, i.e. refer to several targets.
|
|
164
|
+
targets = referenced_expressions
|
|
165
|
+
.map { |exp| exp.unquantified_clone.match_length }
|
|
166
|
+
MatchLength.new(self,
|
|
167
|
+
base_min: targets.map(&:min).min,
|
|
168
|
+
base_max: targets.map(&:max).max,
|
|
169
|
+
reify: ->{ targets.map(&:to_re).join('|') })
|
|
162
170
|
end
|
|
163
171
|
end
|
|
164
172
|
|
|
@@ -101,14 +101,30 @@ module Regexp::Expression
|
|
|
101
101
|
# When changing the conditions, please make sure to update
|
|
102
102
|
# #pretty_print_instance_variables so that it includes all relevant values.
|
|
103
103
|
def ==(other)
|
|
104
|
+
return shallow_equal?(other) if terminal?
|
|
105
|
+
|
|
106
|
+
stack = [[self, other]]
|
|
107
|
+
until stack.empty?
|
|
108
|
+
exp1, exp2 = stack.pop
|
|
109
|
+
return false unless exp1.shallow_equal?(exp2)
|
|
110
|
+
|
|
111
|
+
next if exp1.terminal?
|
|
112
|
+
return false unless exp1.length == exp2.length
|
|
113
|
+
|
|
114
|
+
stack.concat(exp1.zip(exp2))
|
|
115
|
+
end
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
alias :=== :==
|
|
119
|
+
alias :eql? :==
|
|
120
|
+
|
|
121
|
+
# Compares two expressions without considering their subexpressions.
|
|
122
|
+
def shallow_equal?(other)
|
|
104
123
|
self.class == other.class &&
|
|
105
124
|
text == other.text &&
|
|
106
125
|
quantifier == other.quantifier &&
|
|
107
|
-
options == other.options
|
|
108
|
-
(terminal? || expressions == other.expressions)
|
|
126
|
+
options == other.options
|
|
109
127
|
end
|
|
110
|
-
alias :=== :==
|
|
111
|
-
alias :eql? :==
|
|
112
128
|
|
|
113
129
|
def optional?
|
|
114
130
|
quantified? && quantifier.min == 0
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
#
|
|
3
|
+
# This file implements several tree traversal methods.
|
|
4
|
+
#
|
|
5
|
+
# Note: The methods are optimized for performance and avoid stack overflows on
|
|
6
|
+
# large trees. Check tasks/benchmark/traversal.rb when working in here.
|
|
3
7
|
module Regexp::Expression
|
|
4
8
|
class Subexpression < Regexp::Expression::Base
|
|
5
9
|
|
|
@@ -36,14 +40,30 @@ module Regexp::Expression
|
|
|
36
40
|
|
|
37
41
|
block.call(:enter, self, 0) if include_self
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
exp.
|
|
45
|
-
|
|
43
|
+
stack = []
|
|
44
|
+
parent, children, index = self, expressions, 0
|
|
45
|
+
|
|
46
|
+
loop do
|
|
47
|
+
while (exp = children[index])
|
|
48
|
+
if exp.terminal?
|
|
49
|
+
block.call(:visit, exp, index)
|
|
50
|
+
index += 1
|
|
51
|
+
else
|
|
52
|
+
block.call(:enter, exp, index)
|
|
53
|
+
# Resume at this child to emit its exit event after descending.
|
|
54
|
+
stack.push(parent, index)
|
|
55
|
+
parent, children, index = exp, exp.expressions, 0
|
|
56
|
+
end
|
|
46
57
|
end
|
|
58
|
+
|
|
59
|
+
break if stack.empty?
|
|
60
|
+
|
|
61
|
+
exp = parent
|
|
62
|
+
index = stack.pop
|
|
63
|
+
parent = stack.pop
|
|
64
|
+
children = parent.expressions
|
|
65
|
+
block.call(:exit, exp, index)
|
|
66
|
+
index += 1
|
|
47
67
|
end
|
|
48
68
|
|
|
49
69
|
block.call(:exit, self, 0) if include_self
|
|
@@ -66,16 +86,34 @@ module Regexp::Expression
|
|
|
66
86
|
protected
|
|
67
87
|
|
|
68
88
|
def each_expression_with_index(&block)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
stack = []
|
|
90
|
+
children, index = expressions, 0
|
|
91
|
+
|
|
92
|
+
loop do
|
|
93
|
+
while (exp = children[index])
|
|
94
|
+
block.call(exp, index)
|
|
95
|
+
index += 1
|
|
96
|
+
unless exp.terminal?
|
|
97
|
+
# Resume at the next sibling after visiting this child's subtree.
|
|
98
|
+
stack.push(children, index)
|
|
99
|
+
children, index = exp.expressions, 0
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
break if stack.empty?
|
|
104
|
+
|
|
105
|
+
index = stack.pop
|
|
106
|
+
children = stack.pop
|
|
72
107
|
end
|
|
73
108
|
end
|
|
74
109
|
|
|
75
110
|
def each_expression_without_index(&block)
|
|
76
|
-
|
|
111
|
+
queue = expressions.reverse
|
|
112
|
+
|
|
113
|
+
until queue.empty?
|
|
114
|
+
exp = queue.pop
|
|
77
115
|
block.call(exp)
|
|
78
|
-
exp.
|
|
116
|
+
queue.concat(exp.expressions.reverse) unless exp.terminal?
|
|
79
117
|
end
|
|
80
118
|
end
|
|
81
119
|
end
|
|
@@ -11,7 +11,7 @@ module Regexp::Expression
|
|
|
11
11
|
MODES = %i[greedy possessive reluctant].freeze
|
|
12
12
|
|
|
13
13
|
def initialize(*args)
|
|
14
|
-
deprecated_old_init(*args) and return if args.
|
|
14
|
+
deprecated_old_init(*args) and return if args.length == 4 || args.length == 5
|
|
15
15
|
|
|
16
16
|
init_from_token_and_options(*args)
|
|
17
17
|
# TODO: remove in v3.0.0, stop removing parts of #token (?)
|
|
@@ -9,11 +9,9 @@ module Regexp::Expression
|
|
|
9
9
|
extend Shared::ClassMethods
|
|
10
10
|
|
|
11
11
|
attr_accessor :type, :token, :text, :ts, :te,
|
|
12
|
-
:level, :set_level, :conditional_level,
|
|
13
|
-
:options, :parent,
|
|
12
|
+
:level, :set_level, :conditional_level, :nesting_level,
|
|
13
|
+
:options, :parent, :quantifier,
|
|
14
14
|
:custom_to_s_handling, :pre_quantifier_decorations
|
|
15
|
-
|
|
16
|
-
attr_reader :nesting_level, :quantifier
|
|
17
15
|
end
|
|
18
16
|
end
|
|
19
17
|
|
|
@@ -72,15 +70,30 @@ module Regexp::Expression
|
|
|
72
70
|
# lit.to_s(:original) # => 'a +' # with quantifier AND intermittent decorations
|
|
73
71
|
#
|
|
74
72
|
def to_s(format = :full)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
result = ''.dup
|
|
74
|
+
stack = []
|
|
75
|
+
exp, current_parts, index = self, parts, 0
|
|
76
|
+
|
|
77
|
+
loop do
|
|
78
|
+
while index < current_parts.length
|
|
79
|
+
part = current_parts[index]
|
|
80
|
+
index += 1
|
|
81
|
+
if part.instance_of?(String)
|
|
82
|
+
result << part
|
|
83
|
+
elsif !part.custom_to_s_handling
|
|
84
|
+
stack.push([exp, format, current_parts, index])
|
|
85
|
+
exp, format, current_parts, index = part, :original, part.parts, 0
|
|
86
|
+
end
|
|
81
87
|
end
|
|
88
|
+
|
|
89
|
+
result << exp.pre_quantifier_decoration(format).to_s
|
|
90
|
+
result << exp.quantifier_affix(format).to_s
|
|
91
|
+
break if stack.empty?
|
|
92
|
+
|
|
93
|
+
exp, format, current_parts, index = stack.pop
|
|
82
94
|
end
|
|
83
|
-
|
|
95
|
+
|
|
96
|
+
result
|
|
84
97
|
end
|
|
85
98
|
alias :to_str :to_s
|
|
86
99
|
|
|
@@ -99,16 +112,5 @@ module Regexp::Expression
|
|
|
99
112
|
def coded_offset
|
|
100
113
|
'@%d+%d' % offset
|
|
101
114
|
end
|
|
102
|
-
|
|
103
|
-
def nesting_level=(lvl)
|
|
104
|
-
@nesting_level = lvl
|
|
105
|
-
quantifier && quantifier.nesting_level = lvl
|
|
106
|
-
terminal? || each { |subexp| subexp.nesting_level = lvl + 1 }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def quantifier=(qtf)
|
|
110
|
-
@quantifier = qtf
|
|
111
|
-
@repetitions = nil # clear memoized value
|
|
112
|
-
end
|
|
113
115
|
end
|
|
114
116
|
end
|
|
@@ -24,7 +24,7 @@ module Regexp::Expression
|
|
|
24
24
|
expressions << exp
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
%w[[] at each empty? fetch index join last length values_at].each do |method|
|
|
27
|
+
%w[[] at each empty? fetch index join last length values_at zip].each do |method|
|
|
28
28
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
29
29
|
def #{method}(*args, &block)
|
|
30
30
|
expressions.#{method}(*args, &block)
|
|
@@ -51,7 +51,7 @@ module Regexp::Expression
|
|
|
51
51
|
|
|
52
52
|
def extract_quantifier_target(quantifier_description)
|
|
53
53
|
pre_quantifier_decorations = []
|
|
54
|
-
target = expressions.
|
|
54
|
+
target = expressions.reverse_each.find do |exp|
|
|
55
55
|
if exp.decorative?
|
|
56
56
|
exp.custom_to_s_handling = true
|
|
57
57
|
pre_quantifier_decorations << exp.text
|
data/lib/regexp_parser/lexer.rb
CHANGED
|
@@ -5,19 +5,17 @@
|
|
|
5
5
|
# normalizes tokens for the parser, and checks if they are implemented by the
|
|
6
6
|
# given syntax flavor.
|
|
7
7
|
class Regexp::Lexer
|
|
8
|
-
|
|
9
8
|
OPENING_TOKENS = %i[
|
|
10
9
|
capture passive lookahead nlookahead lookbehind nlookbehind
|
|
11
10
|
atomic options options_switch named absence open
|
|
12
11
|
].freeze
|
|
13
12
|
|
|
14
|
-
CLOSING_TOKENS = %i[close].freeze
|
|
15
|
-
|
|
16
13
|
CONDITION_TOKENS = %i[condition condition_close].freeze
|
|
17
14
|
|
|
18
15
|
def self.lex(input, syntax = nil, options: nil, collect_tokens: true, &block)
|
|
19
16
|
new.lex(input, syntax, options: options, collect_tokens: collect_tokens, &block)
|
|
20
17
|
end
|
|
18
|
+
class << self; alias :scan :lex end
|
|
21
19
|
|
|
22
20
|
def lex(input, syntax = nil, options: nil, collect_tokens: true, &block)
|
|
23
21
|
syntax = syntax ? Regexp::Syntax.for(syntax) : Regexp::Syntax::CURRENT
|
|
@@ -33,19 +31,21 @@ class Regexp::Lexer
|
|
|
33
31
|
self.shift = 0
|
|
34
32
|
|
|
35
33
|
Regexp::Scanner.scan(input, options: options, collect_tokens: false) do |type, token, text, ts, te|
|
|
36
|
-
type, token =
|
|
34
|
+
type, token = syntax.normalize(type, token)
|
|
37
35
|
syntax.check! type, token
|
|
38
36
|
|
|
39
37
|
ascend(type, token)
|
|
40
38
|
|
|
41
|
-
if (last = prev_token) &&
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
if (last = prev_token) && type == :quantifier
|
|
40
|
+
shortened_run, quantified_node =
|
|
41
|
+
if last.type == :literal then break_literal_run(last)
|
|
42
|
+
elsif last.token == :codepoint_list then break_codepoint_list(last)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if shortened_run
|
|
46
|
+
emit(shortened_run)
|
|
47
|
+
last = quantified_node
|
|
48
|
+
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
current = Regexp::Token.new(type, token, text, ts + shift, te + shift,
|
|
@@ -80,10 +80,6 @@ class Regexp::Lexer
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
class << self
|
|
84
|
-
alias :scan :lex
|
|
85
|
-
end
|
|
86
|
-
|
|
87
83
|
private
|
|
88
84
|
|
|
89
85
|
attr_accessor :block,
|
|
@@ -91,7 +87,7 @@ class Regexp::Lexer
|
|
|
91
87
|
:nesting, :set_nesting, :conditional_nesting, :shift
|
|
92
88
|
|
|
93
89
|
def ascend(type, token)
|
|
94
|
-
return unless
|
|
90
|
+
return unless token == :close
|
|
95
91
|
|
|
96
92
|
case type
|
|
97
93
|
when :group, :assertion
|
|
@@ -122,7 +118,7 @@ class Regexp::Lexer
|
|
|
122
118
|
|
|
123
119
|
# called by scan to break a literal run that is longer than one character
|
|
124
120
|
# into two separate tokens when it is followed by a quantifier
|
|
125
|
-
def
|
|
121
|
+
def break_literal_run(token)
|
|
126
122
|
lead, last, _ = token.text.partition(/.\z/mu)
|
|
127
123
|
return if lead.empty?
|
|
128
124
|
|
|
@@ -141,7 +137,7 @@ class Regexp::Lexer
|
|
|
141
137
|
|
|
142
138
|
# if a codepoint list is followed by a quantifier, that quantifier applies
|
|
143
139
|
# to the last codepoint, e.g. /\u{61 62 63}{3}/ =~ 'abccc'
|
|
144
|
-
# c.f. #
|
|
140
|
+
# c.f. #break_literal_run.
|
|
145
141
|
def break_codepoint_list(token)
|
|
146
142
|
lead, _, tail = token.text.rpartition(' ')
|
|
147
143
|
return if lead.empty?
|
|
@@ -153,7 +149,11 @@ class Regexp::Lexer
|
|
|
153
149
|
(token.ts + lead.length + 1), (token.te + 3),
|
|
154
150
|
nesting, set_nesting, conditional_nesting)
|
|
155
151
|
|
|
156
|
-
|
|
152
|
+
# Splitting the codepoint list into two parts changes the correct offsets
|
|
153
|
+
# of any following tokens because it requires extra chars to represent the
|
|
154
|
+
# 'virtual' token, e.g. `\u{61 62 63}{3}` is lexed as `\u{61 62}\u{63}{3}`,
|
|
155
|
+
# which is 3 chars longer.
|
|
156
|
+
self.shift = shift + 3
|
|
157
157
|
|
|
158
158
|
token_1.previous = preprev_token
|
|
159
159
|
token_1.next = token_2
|
|
@@ -167,5 +167,4 @@ class Regexp::Lexer
|
|
|
167
167
|
token.previous = preprev_token # .next will be set by #lex
|
|
168
168
|
token
|
|
169
169
|
end
|
|
170
|
-
|
|
171
|
-
end # module Regexp::Lexer
|
|
170
|
+
end
|
data/lib/regexp_parser/parser.rb
CHANGED
|
@@ -36,15 +36,14 @@ class Regexp::Parser
|
|
|
36
36
|
self.conditional_nesting = []
|
|
37
37
|
|
|
38
38
|
self.captured_group_counts = Hash.new(0)
|
|
39
|
+
self.total_captured_group_count = 0
|
|
39
40
|
|
|
40
|
-
Regexp::Lexer.
|
|
41
|
+
Regexp::Lexer.new.lex(input, syntax, options: options, collect_tokens: false) do |token|
|
|
41
42
|
parse_token(token)
|
|
42
43
|
end
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
root.nesting_level = 0
|
|
47
|
-
assign_referenced_expressions
|
|
45
|
+
root.assign_referenced_expressions
|
|
46
|
+
root.recursively_update_nesting_levels
|
|
48
47
|
|
|
49
48
|
if block_given?
|
|
50
49
|
block.call(root)
|
|
@@ -57,7 +56,7 @@ class Regexp::Parser
|
|
|
57
56
|
|
|
58
57
|
attr_accessor :root, :node, :nesting,
|
|
59
58
|
:options_stack, :switching_options, :conditional_nesting,
|
|
60
|
-
:captured_group_counts
|
|
59
|
+
:captured_group_counts, :total_captured_group_count
|
|
61
60
|
|
|
62
61
|
def extract_options(input, options)
|
|
63
62
|
if options && !input.is_a?(String)
|
|
@@ -197,16 +196,13 @@ class Regexp::Parser
|
|
|
197
196
|
nest(group)
|
|
198
197
|
end
|
|
199
198
|
|
|
200
|
-
def total_captured_group_count
|
|
201
|
-
captured_group_counts.values.reduce(0, :+)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
199
|
def captured_group_count_at_level
|
|
205
200
|
captured_group_counts[node]
|
|
206
201
|
end
|
|
207
202
|
|
|
208
203
|
def count_captured_group
|
|
209
204
|
captured_group_counts[node] += 1
|
|
205
|
+
self.total_captured_group_count = total_captured_group_count + 1
|
|
210
206
|
end
|
|
211
207
|
|
|
212
208
|
def close_group
|
|
@@ -496,7 +492,10 @@ class Regexp::Parser
|
|
|
496
492
|
)
|
|
497
493
|
new_group.implicit = true
|
|
498
494
|
new_group << target_node
|
|
499
|
-
|
|
495
|
+
increment_group_level(target_node)
|
|
496
|
+
unless target_node.terminal?
|
|
497
|
+
target_node.each_expression { |exp| increment_group_level(exp) }
|
|
498
|
+
end
|
|
500
499
|
node.expressions[node.expressions.index(target_node)] = new_group
|
|
501
500
|
target_node = new_group
|
|
502
501
|
end
|
|
@@ -509,10 +508,9 @@ class Regexp::Parser
|
|
|
509
508
|
target_node.quantify(token, active_opts)
|
|
510
509
|
end
|
|
511
510
|
|
|
512
|
-
def
|
|
511
|
+
def increment_group_level(exp)
|
|
513
512
|
exp.level += 1
|
|
514
513
|
exp.quantifier.level += 1 if exp.quantifier
|
|
515
|
-
exp.terminal? || exp.each { |subexp| increase_group_level(subexp) }
|
|
516
514
|
end
|
|
517
515
|
|
|
518
516
|
def set(token)
|
|
@@ -577,26 +575,4 @@ class Regexp::Parser
|
|
|
577
575
|
def active_opts
|
|
578
576
|
options_stack.last
|
|
579
577
|
end
|
|
580
|
-
|
|
581
|
-
# Assigns referenced expressions to referring expressions, e.g. if there is
|
|
582
|
-
# an instance of Backreference::Number, its #referenced_expression is set to
|
|
583
|
-
# the instance of Group::Capture that it refers to via its number.
|
|
584
|
-
def assign_referenced_expressions
|
|
585
|
-
# find all referenceable and referring expressions
|
|
586
|
-
targets = { 0 => [root] }
|
|
587
|
-
referrers = []
|
|
588
|
-
root.each_expression do |exp|
|
|
589
|
-
if exp.referential?
|
|
590
|
-
referrers << exp
|
|
591
|
-
elsif exp.is_a?(Group::Capture)
|
|
592
|
-
(targets[exp.identifier] ||= []) << exp
|
|
593
|
-
end
|
|
594
|
-
end
|
|
595
|
-
# assign referenced expressions to referring expressions
|
|
596
|
-
# (in a second iteration because there might be forward references)
|
|
597
|
-
referrers.each do |exp|
|
|
598
|
-
exp.referenced_expressions = targets[exp.reference] ||
|
|
599
|
-
raise(ParserError, "Invalid reference #{exp.reference} at pos #{exp.ts}")
|
|
600
|
-
end
|
|
601
|
-
end
|
|
602
|
-
end # module Regexp::Parser
|
|
578
|
+
end
|