/rust/registry/src/index.crates.io-1949cf8c6b5b557f/regex-1.5.6/src/backtrack.rs
Line | Count | Source |
1 | | // This is the backtracking matching engine. It has the same exact capability |
2 | | // as the full NFA simulation, except it is artificially restricted to small |
3 | | // regexes on small inputs because of its memory requirements. |
4 | | // |
5 | | // In particular, this is a *bounded* backtracking engine. It retains worst |
6 | | // case linear time by keeping track of the states that it has visited (using a |
7 | | // bitmap). Namely, once a state is visited, it is never visited again. Since a |
8 | | // state is keyed by `(instruction index, input index)`, we have that its time |
9 | | // complexity is `O(mn)` (i.e., linear in the size of the search text). |
10 | | // |
11 | | // The backtracking engine can beat out the NFA simulation on small |
12 | | // regexes/inputs because it doesn't have to keep track of multiple copies of |
13 | | // the capture groups. In benchmarks, the backtracking engine is roughly twice |
14 | | // as fast as the full NFA simulation. Note though that its performance doesn't |
15 | | // scale, even if you're willing to live with the memory requirements. Namely, |
16 | | // the bitset has to be zeroed on each execution, which becomes quite expensive |
17 | | // on large bitsets. |
18 | | |
19 | | use crate::exec::ProgramCache; |
20 | | use crate::input::{Input, InputAt}; |
21 | | use crate::prog::{InstPtr, Program}; |
22 | | use crate::re_trait::Slot; |
23 | | |
24 | | type Bits = u32; |
25 | | |
26 | | const BIT_SIZE: usize = 32; |
27 | | const MAX_SIZE_BYTES: usize = 256 * (1 << 10); // 256 KB |
28 | | |
29 | | /// Returns true iff the given regex and input should be executed by this |
30 | | /// engine with reasonable memory usage. |
31 | 66.6k | pub fn should_exec(num_insts: usize, text_len: usize) -> bool { |
32 | | // Total memory usage in bytes is determined by: |
33 | | // |
34 | | // ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32)) |
35 | | // |
36 | | // The actual limit picked is pretty much a heuristic. |
37 | | // See: https://github.com/rust-lang/regex/issues/215 |
38 | 66.6k | let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4; |
39 | 66.6k | size <= MAX_SIZE_BYTES |
40 | 66.6k | } regex::backtrack::should_exec Line | Count | Source | 31 | 9.11k | pub fn should_exec(num_insts: usize, text_len: usize) -> bool { | 32 | | // Total memory usage in bytes is determined by: | 33 | | // | 34 | | // ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32)) | 35 | | // | 36 | | // The actual limit picked is pretty much a heuristic. | 37 | | // See: https://github.com/rust-lang/regex/issues/215 | 38 | 9.11k | let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4; | 39 | 9.11k | size <= MAX_SIZE_BYTES | 40 | 9.11k | } |
regex::backtrack::should_exec Line | Count | Source | 31 | 57.5k | pub fn should_exec(num_insts: usize, text_len: usize) -> bool { | 32 | | // Total memory usage in bytes is determined by: | 33 | | // | 34 | | // ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32)) | 35 | | // | 36 | | // The actual limit picked is pretty much a heuristic. | 37 | | // See: https://github.com/rust-lang/regex/issues/215 | 38 | 57.5k | let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4; | 39 | 57.5k | size <= MAX_SIZE_BYTES | 40 | 57.5k | } |
|
41 | | |
42 | | /// A backtracking matching engine. |
43 | | #[derive(Debug)] |
44 | | pub struct Bounded<'a, 'm, 'r, 's, I> { |
45 | | prog: &'r Program, |
46 | | input: I, |
47 | | matches: &'m mut [bool], |
48 | | slots: &'s mut [Slot], |
49 | | m: &'a mut Cache, |
50 | | } |
51 | | |
52 | | /// Shared cached state between multiple invocations of a backtracking engine |
53 | | /// in the same thread. |
54 | | #[derive(Clone, Debug)] |
55 | | pub struct Cache { |
56 | | jobs: Vec<Job>, |
57 | | visited: Vec<Bits>, |
58 | | } |
59 | | |
60 | | impl Cache { |
61 | | /// Create new empty cache for the backtracking engine. |
62 | 18 | pub fn new(_prog: &Program) -> Self { |
63 | 18 | Cache { jobs: vec![], visited: vec![] } |
64 | 18 | } <regex::backtrack::Cache>::new Line | Count | Source | 62 | 9 | pub fn new(_prog: &Program) -> Self { | 63 | 9 | Cache { jobs: vec![], visited: vec![] } | 64 | 9 | } |
<regex::backtrack::Cache>::new Line | Count | Source | 62 | 9 | pub fn new(_prog: &Program) -> Self { | 63 | 9 | Cache { jobs: vec![], visited: vec![] } | 64 | 9 | } |
|
65 | | } |
66 | | |
67 | | /// A job is an explicit unit of stack space in the backtracking engine. |
68 | | /// |
69 | | /// The "normal" representation is a single state transition, which corresponds |
70 | | /// to an NFA state and a character in the input. However, the backtracking |
71 | | /// engine must keep track of old capture group values. We use the explicit |
72 | | /// stack to do it. |
73 | | #[derive(Clone, Copy, Debug)] |
74 | | enum Job { |
75 | | Inst { ip: InstPtr, at: InputAt }, |
76 | | SaveRestore { slot: usize, old_pos: Option<usize> }, |
77 | | } |
78 | | |
79 | | impl<'a, 'm, 'r, 's, I: Input> Bounded<'a, 'm, 'r, 's, I> { |
80 | | /// Execute the backtracking matching engine. |
81 | | /// |
82 | | /// If there's a match, `exec` returns `true` and populates the given |
83 | | /// captures accordingly. |
84 | 66.6k | pub fn exec( |
85 | 66.6k | prog: &'r Program, |
86 | 66.6k | cache: &ProgramCache, |
87 | 66.6k | matches: &'m mut [bool], |
88 | 66.6k | slots: &'s mut [Slot], |
89 | 66.6k | input: I, |
90 | 66.6k | start: usize, |
91 | 66.6k | end: usize, |
92 | 66.6k | ) -> bool { |
93 | 66.6k | let mut cache = cache.borrow_mut(); |
94 | 66.6k | let cache = &mut cache.backtrack; |
95 | 66.6k | let start = input.at(start); |
96 | 66.6k | let mut b = Bounded { |
97 | 66.6k | prog: prog, |
98 | 66.6k | input: input, |
99 | 66.6k | matches: matches, |
100 | 66.6k | slots: slots, |
101 | 66.6k | m: cache, |
102 | 66.6k | }; |
103 | 66.6k | b.exec_(start, end) |
104 | 66.6k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec <regex::backtrack::Bounded<regex::input::CharInput>>::exec Line | Count | Source | 84 | 9.11k | pub fn exec( | 85 | 9.11k | prog: &'r Program, | 86 | 9.11k | cache: &ProgramCache, | 87 | 9.11k | matches: &'m mut [bool], | 88 | 9.11k | slots: &'s mut [Slot], | 89 | 9.11k | input: I, | 90 | 9.11k | start: usize, | 91 | 9.11k | end: usize, | 92 | 9.11k | ) -> bool { | 93 | 9.11k | let mut cache = cache.borrow_mut(); | 94 | 9.11k | let cache = &mut cache.backtrack; | 95 | 9.11k | let start = input.at(start); | 96 | 9.11k | let mut b = Bounded { | 97 | 9.11k | prog: prog, | 98 | 9.11k | input: input, | 99 | 9.11k | matches: matches, | 100 | 9.11k | slots: slots, | 101 | 9.11k | m: cache, | 102 | 9.11k | }; | 103 | 9.11k | b.exec_(start, end) | 104 | 9.11k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec <regex::backtrack::Bounded<regex::input::CharInput>>::exec Line | Count | Source | 84 | 57.5k | pub fn exec( | 85 | 57.5k | prog: &'r Program, | 86 | 57.5k | cache: &ProgramCache, | 87 | 57.5k | matches: &'m mut [bool], | 88 | 57.5k | slots: &'s mut [Slot], | 89 | 57.5k | input: I, | 90 | 57.5k | start: usize, | 91 | 57.5k | end: usize, | 92 | 57.5k | ) -> bool { | 93 | 57.5k | let mut cache = cache.borrow_mut(); | 94 | 57.5k | let cache = &mut cache.backtrack; | 95 | 57.5k | let start = input.at(start); | 96 | 57.5k | let mut b = Bounded { | 97 | 57.5k | prog: prog, | 98 | 57.5k | input: input, | 99 | 57.5k | matches: matches, | 100 | 57.5k | slots: slots, | 101 | 57.5k | m: cache, | 102 | 57.5k | }; | 103 | 57.5k | b.exec_(start, end) | 104 | 57.5k | } |
|
105 | | |
106 | | /// Clears the cache such that the backtracking engine can be executed |
107 | | /// on some input of fixed length. |
108 | 66.6k | fn clear(&mut self) { |
109 | | // Reset the job memory so that we start fresh. |
110 | 66.6k | self.m.jobs.clear(); |
111 | | |
112 | | // Now we need to clear the bit state set. |
113 | | // We do this by figuring out how much space we need to keep track |
114 | | // of the states we've visited. |
115 | | // Then we reset all existing allocated space to 0. |
116 | | // Finally, we request more space if we need it. |
117 | | // |
118 | | // This is all a little circuitous, but doing this using unchecked |
119 | | // operations doesn't seem to have a measurable impact on performance. |
120 | | // (Probably because backtracking is limited to such small |
121 | | // inputs/regexes in the first place.) |
122 | 66.6k | let visited_len = |
123 | 66.6k | (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1) |
124 | 66.6k | / BIT_SIZE; |
125 | 66.6k | self.m.visited.truncate(visited_len); |
126 | 3.79M | for v in &mut self.m.visited { |
127 | 3.72M | *v = 0; |
128 | 3.72M | } |
129 | 66.6k | if visited_len > self.m.visited.len() { |
130 | 19.6k | let len = self.m.visited.len(); |
131 | 19.6k | self.m.visited.reserve_exact(visited_len - len); |
132 | 909k | for _ in 0..(visited_len - len) { |
133 | 909k | self.m.visited.push(0); |
134 | 909k | } |
135 | 46.9k | } |
136 | 66.6k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::clear <regex::backtrack::Bounded<regex::input::CharInput>>::clear Line | Count | Source | 108 | 9.11k | fn clear(&mut self) { | 109 | | // Reset the job memory so that we start fresh. | 110 | 9.11k | self.m.jobs.clear(); | 111 | | | 112 | | // Now we need to clear the bit state set. | 113 | | // We do this by figuring out how much space we need to keep track | 114 | | // of the states we've visited. | 115 | | // Then we reset all existing allocated space to 0. | 116 | | // Finally, we request more space if we need it. | 117 | | // | 118 | | // This is all a little circuitous, but doing this using unchecked | 119 | | // operations doesn't seem to have a measurable impact on performance. | 120 | | // (Probably because backtracking is limited to such small | 121 | | // inputs/regexes in the first place.) | 122 | 9.11k | let visited_len = | 123 | 9.11k | (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1) | 124 | 9.11k | / BIT_SIZE; | 125 | 9.11k | self.m.visited.truncate(visited_len); | 126 | 613k | for v in &mut self.m.visited { | 127 | 604k | *v = 0; | 128 | 604k | } | 129 | 9.11k | if visited_len > self.m.visited.len() { | 130 | 2.49k | let len = self.m.visited.len(); | 131 | 2.49k | self.m.visited.reserve_exact(visited_len - len); | 132 | 158k | for _ in 0..(visited_len - len) { | 133 | 158k | self.m.visited.push(0); | 134 | 158k | } | 135 | 6.62k | } | 136 | 9.11k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::clear <regex::backtrack::Bounded<regex::input::CharInput>>::clear Line | Count | Source | 108 | 57.5k | fn clear(&mut self) { | 109 | | // Reset the job memory so that we start fresh. | 110 | 57.5k | self.m.jobs.clear(); | 111 | | | 112 | | // Now we need to clear the bit state set. | 113 | | // We do this by figuring out how much space we need to keep track | 114 | | // of the states we've visited. | 115 | | // Then we reset all existing allocated space to 0. | 116 | | // Finally, we request more space if we need it. | 117 | | // | 118 | | // This is all a little circuitous, but doing this using unchecked | 119 | | // operations doesn't seem to have a measurable impact on performance. | 120 | | // (Probably because backtracking is limited to such small | 121 | | // inputs/regexes in the first place.) | 122 | 57.5k | let visited_len = | 123 | 57.5k | (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1) | 124 | 57.5k | / BIT_SIZE; | 125 | 57.5k | self.m.visited.truncate(visited_len); | 126 | 3.17M | for v in &mut self.m.visited { | 127 | 3.12M | *v = 0; | 128 | 3.12M | } | 129 | 57.5k | if visited_len > self.m.visited.len() { | 130 | 17.1k | let len = self.m.visited.len(); | 131 | 17.1k | self.m.visited.reserve_exact(visited_len - len); | 132 | 751k | for _ in 0..(visited_len - len) { | 133 | 751k | self.m.visited.push(0); | 134 | 751k | } | 135 | 40.3k | } | 136 | 57.5k | } |
|
137 | | |
138 | | /// Start backtracking at the given position in the input, but also look |
139 | | /// for literal prefixes. |
140 | 66.6k | fn exec_(&mut self, mut at: InputAt, end: usize) -> bool { |
141 | 66.6k | self.clear(); |
142 | | // If this is an anchored regex at the beginning of the input, then |
143 | | // we're either already done or we only need to try backtracking once. |
144 | 66.6k | if self.prog.is_anchored_start { |
145 | 66.6k | return if !at.is_start() { false } else { self.backtrack(at) }; |
146 | 0 | } |
147 | 0 | let mut matched = false; |
148 | | loop { |
149 | 0 | if !self.prog.prefixes.is_empty() { |
150 | 0 | at = match self.input.prefix_at(&self.prog.prefixes, at) { |
151 | 0 | None => break, |
152 | 0 | Some(at) => at, |
153 | | }; |
154 | 0 | } |
155 | 0 | matched = self.backtrack(at) || matched; |
156 | 0 | if matched && self.prog.matches.len() == 1 { |
157 | 0 | return true; |
158 | 0 | } |
159 | 0 | if at.pos() >= end { |
160 | 0 | break; |
161 | 0 | } |
162 | 0 | at = self.input.at(at.next_pos()); |
163 | | } |
164 | 0 | matched |
165 | 66.6k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec_ <regex::backtrack::Bounded<regex::input::CharInput>>::exec_ Line | Count | Source | 140 | 9.11k | fn exec_(&mut self, mut at: InputAt, end: usize) -> bool { | 141 | 9.11k | self.clear(); | 142 | | // If this is an anchored regex at the beginning of the input, then | 143 | | // we're either already done or we only need to try backtracking once. | 144 | 9.11k | if self.prog.is_anchored_start { | 145 | 9.11k | return if !at.is_start() { false } else { self.backtrack(at) }; | 146 | 0 | } | 147 | 0 | let mut matched = false; | 148 | | loop { | 149 | 0 | if !self.prog.prefixes.is_empty() { | 150 | 0 | at = match self.input.prefix_at(&self.prog.prefixes, at) { | 151 | 0 | None => break, | 152 | 0 | Some(at) => at, | 153 | | }; | 154 | 0 | } | 155 | 0 | matched = self.backtrack(at) || matched; | 156 | 0 | if matched && self.prog.matches.len() == 1 { | 157 | 0 | return true; | 158 | 0 | } | 159 | 0 | if at.pos() >= end { | 160 | 0 | break; | 161 | 0 | } | 162 | 0 | at = self.input.at(at.next_pos()); | 163 | | } | 164 | 0 | matched | 165 | 9.11k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec_ <regex::backtrack::Bounded<regex::input::CharInput>>::exec_ Line | Count | Source | 140 | 57.5k | fn exec_(&mut self, mut at: InputAt, end: usize) -> bool { | 141 | 57.5k | self.clear(); | 142 | | // If this is an anchored regex at the beginning of the input, then | 143 | | // we're either already done or we only need to try backtracking once. | 144 | 57.5k | if self.prog.is_anchored_start { | 145 | 57.5k | return if !at.is_start() { false } else { self.backtrack(at) }; | 146 | 0 | } | 147 | 0 | let mut matched = false; | 148 | | loop { | 149 | 0 | if !self.prog.prefixes.is_empty() { | 150 | 0 | at = match self.input.prefix_at(&self.prog.prefixes, at) { | 151 | 0 | None => break, | 152 | 0 | Some(at) => at, | 153 | | }; | 154 | 0 | } | 155 | 0 | matched = self.backtrack(at) || matched; | 156 | 0 | if matched && self.prog.matches.len() == 1 { | 157 | 0 | return true; | 158 | 0 | } | 159 | 0 | if at.pos() >= end { | 160 | 0 | break; | 161 | 0 | } | 162 | 0 | at = self.input.at(at.next_pos()); | 163 | | } | 164 | 0 | matched | 165 | 57.5k | } |
|
166 | | |
167 | | /// The main backtracking loop starting at the given input position. |
168 | 66.6k | fn backtrack(&mut self, start: InputAt) -> bool { |
169 | | // N.B. We use an explicit stack to avoid recursion. |
170 | | // To avoid excessive pushing and popping, most transitions are handled |
171 | | // in the `step` helper function, which only pushes to the stack when |
172 | | // there's a capture or a branch. |
173 | 66.6k | let mut matched = false; |
174 | 66.6k | self.m.jobs.push(Job::Inst { ip: 0, at: start }); |
175 | 771k | while let Some(job) = self.m.jobs.pop() { |
176 | 730k | match job { |
177 | 647k | Job::Inst { ip, at } => { |
178 | 647k | if self.step(ip, at) { |
179 | | // Only quit if we're matching one regex. |
180 | | // If we're matching a regex set, then mush on and |
181 | | // try to find other matches (if we want them). |
182 | 24.9k | if self.prog.matches.len() == 1 { |
183 | 24.9k | return true; |
184 | 0 | } |
185 | 0 | matched = true; |
186 | 622k | } |
187 | | } |
188 | 82.3k | Job::SaveRestore { slot, old_pos } => { |
189 | 82.3k | if slot < self.slots.len() { |
190 | 82.3k | self.slots[slot] = old_pos; |
191 | 82.3k | } |
192 | | } |
193 | | } |
194 | | } |
195 | 41.7k | matched |
196 | 66.6k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::backtrack <regex::backtrack::Bounded<regex::input::CharInput>>::backtrack Line | Count | Source | 168 | 9.11k | fn backtrack(&mut self, start: InputAt) -> bool { | 169 | | // N.B. We use an explicit stack to avoid recursion. | 170 | | // To avoid excessive pushing and popping, most transitions are handled | 171 | | // in the `step` helper function, which only pushes to the stack when | 172 | | // there's a capture or a branch. | 173 | 9.11k | let mut matched = false; | 174 | 9.11k | self.m.jobs.push(Job::Inst { ip: 0, at: start }); | 175 | 89.3k | while let Some(job) = self.m.jobs.pop() { | 176 | 83.5k | match job { | 177 | 74.9k | Job::Inst { ip, at } => { | 178 | 74.9k | if self.step(ip, at) { | 179 | | // Only quit if we're matching one regex. | 180 | | // If we're matching a regex set, then mush on and | 181 | | // try to find other matches (if we want them). | 182 | 3.31k | if self.prog.matches.len() == 1 { | 183 | 3.31k | return true; | 184 | 0 | } | 185 | 0 | matched = true; | 186 | 71.6k | } | 187 | | } | 188 | 8.63k | Job::SaveRestore { slot, old_pos } => { | 189 | 8.63k | if slot < self.slots.len() { | 190 | 8.63k | self.slots[slot] = old_pos; | 191 | 8.63k | } | 192 | | } | 193 | | } | 194 | | } | 195 | 5.80k | matched | 196 | 9.11k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::backtrack <regex::backtrack::Bounded<regex::input::CharInput>>::backtrack Line | Count | Source | 168 | 57.5k | fn backtrack(&mut self, start: InputAt) -> bool { | 169 | | // N.B. We use an explicit stack to avoid recursion. | 170 | | // To avoid excessive pushing and popping, most transitions are handled | 171 | | // in the `step` helper function, which only pushes to the stack when | 172 | | // there's a capture or a branch. | 173 | 57.5k | let mut matched = false; | 174 | 57.5k | self.m.jobs.push(Job::Inst { ip: 0, at: start }); | 175 | 682k | while let Some(job) = self.m.jobs.pop() { | 176 | 646k | match job { | 177 | 572k | Job::Inst { ip, at } => { | 178 | 572k | if self.step(ip, at) { | 179 | | // Only quit if we're matching one regex. | 180 | | // If we're matching a regex set, then mush on and | 181 | | // try to find other matches (if we want them). | 182 | 21.6k | if self.prog.matches.len() == 1 { | 183 | 21.6k | return true; | 184 | 0 | } | 185 | 0 | matched = true; | 186 | 551k | } | 187 | | } | 188 | 73.7k | Job::SaveRestore { slot, old_pos } => { | 189 | 73.7k | if slot < self.slots.len() { | 190 | 73.7k | self.slots[slot] = old_pos; | 191 | 73.7k | } | 192 | | } | 193 | | } | 194 | | } | 195 | 35.9k | matched | 196 | 57.5k | } |
|
197 | | |
198 | 647k | fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool { |
199 | | use crate::prog::Inst::*; |
200 | | loop { |
201 | | // This loop is an optimization to avoid constantly pushing/popping |
202 | | // from the stack. Namely, if we're pushing a job only to run it |
203 | | // next, avoid the push and just mutate `ip` (and possibly `at`) |
204 | | // in place. |
205 | 2.52M | if self.has_visited(ip, at) { |
206 | 11.6k | return false; |
207 | 2.51M | } |
208 | 2.51M | match self.prog[ip] { |
209 | 24.9k | Match(slot) => { |
210 | 24.9k | if slot < self.matches.len() { |
211 | 24.9k | self.matches[slot] = true; |
212 | 24.9k | } |
213 | 24.9k | return true; |
214 | | } |
215 | 217k | Save(ref inst) => { |
216 | 217k | if let Some(&old_pos) = self.slots.get(inst.slot) { |
217 | 217k | // If this path doesn't work out, then we save the old |
218 | 217k | // capture index (if one exists) in an alternate |
219 | 217k | // job. If the next path fails, then the alternate |
220 | 217k | // job is popped and the old capture index is restored. |
221 | 217k | self.m.jobs.push(Job::SaveRestore { |
222 | 217k | slot: inst.slot, |
223 | 217k | old_pos: old_pos, |
224 | 217k | }); |
225 | 217k | self.slots[inst.slot] = Some(at.pos()); |
226 | 217k | } |
227 | 217k | ip = inst.goto; |
228 | | } |
229 | 906k | Split(ref inst) => { |
230 | 906k | self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at }); |
231 | 906k | ip = inst.goto1; |
232 | 906k | } |
233 | 121k | EmptyLook(ref inst) => { |
234 | 121k | if self.input.is_empty_match(at, inst) { |
235 | 91.5k | ip = inst.goto; |
236 | 91.5k | } else { |
237 | 29.4k | return false; |
238 | | } |
239 | | } |
240 | 542k | Char(ref inst) => { |
241 | 542k | if inst.c == at.char() { |
242 | 287k | ip = inst.goto; |
243 | 287k | at = self.input.at(at.next_pos()); |
244 | 287k | } else { |
245 | 255k | return false; |
246 | | } |
247 | | } |
248 | 704k | Ranges(ref inst) => { |
249 | 704k | if inst.matches(at.char()) { |
250 | 378k | ip = inst.goto; |
251 | 378k | at = self.input.at(at.next_pos()); |
252 | 378k | } else { |
253 | 326k | return false; |
254 | | } |
255 | | } |
256 | 0 | Bytes(ref inst) => { |
257 | 0 | if let Some(b) = at.byte() { |
258 | 0 | if inst.matches(b) { |
259 | 0 | ip = inst.goto; |
260 | 0 | at = self.input.at(at.next_pos()); |
261 | 0 | continue; |
262 | 0 | } |
263 | 0 | } |
264 | 0 | return false; |
265 | | } |
266 | | } |
267 | | } |
268 | 647k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::step <regex::backtrack::Bounded<regex::input::CharInput>>::step Line | Count | Source | 198 | 74.9k | fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool { | 199 | | use crate::prog::Inst::*; | 200 | | loop { | 201 | | // This loop is an optimization to avoid constantly pushing/popping | 202 | | // from the stack. Namely, if we're pushing a job only to run it | 203 | | // next, avoid the push and just mutate `ip` (and possibly `at`) | 204 | | // in place. | 205 | 303k | if self.has_visited(ip, at) { | 206 | 745 | return false; | 207 | 302k | } | 208 | 302k | match self.prog[ip] { | 209 | 3.31k | Match(slot) => { | 210 | 3.31k | if slot < self.matches.len() { | 211 | 3.31k | self.matches[slot] = true; | 212 | 3.31k | } | 213 | 3.31k | return true; | 214 | | } | 215 | 26.7k | Save(ref inst) => { | 216 | 26.7k | if let Some(&old_pos) = self.slots.get(inst.slot) { | 217 | 26.7k | // If this path doesn't work out, then we save the old | 218 | 26.7k | // capture index (if one exists) in an alternate | 219 | 26.7k | // job. If the next path fails, then the alternate | 220 | 26.7k | // job is popped and the old capture index is restored. | 221 | 26.7k | self.m.jobs.push(Job::SaveRestore { | 222 | 26.7k | slot: inst.slot, | 223 | 26.7k | old_pos: old_pos, | 224 | 26.7k | }); | 225 | 26.7k | self.slots[inst.slot] = Some(at.pos()); | 226 | 26.7k | } | 227 | 26.7k | ip = inst.goto; | 228 | | } | 229 | 106k | Split(ref inst) => { | 230 | 106k | self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at }); | 231 | 106k | ip = inst.goto1; | 232 | 106k | } | 233 | 13.3k | EmptyLook(ref inst) => { | 234 | 13.3k | if self.input.is_empty_match(at, inst) { | 235 | 12.4k | ip = inst.goto; | 236 | 12.4k | } else { | 237 | 879 | return false; | 238 | | } | 239 | | } | 240 | 71.2k | Char(ref inst) => { | 241 | 71.2k | if inst.c == at.char() { | 242 | 39.6k | ip = inst.goto; | 243 | 39.6k | at = self.input.at(at.next_pos()); | 244 | 39.6k | } else { | 245 | 31.6k | return false; | 246 | | } | 247 | | } | 248 | 81.5k | Ranges(ref inst) => { | 249 | 81.5k | if inst.matches(at.char()) { | 250 | 43.1k | ip = inst.goto; | 251 | 43.1k | at = self.input.at(at.next_pos()); | 252 | 43.1k | } else { | 253 | 38.4k | return false; | 254 | | } | 255 | | } | 256 | 0 | Bytes(ref inst) => { | 257 | 0 | if let Some(b) = at.byte() { | 258 | 0 | if inst.matches(b) { | 259 | 0 | ip = inst.goto; | 260 | 0 | at = self.input.at(at.next_pos()); | 261 | 0 | continue; | 262 | 0 | } | 263 | 0 | } | 264 | 0 | return false; | 265 | | } | 266 | | } | 267 | | } | 268 | 74.9k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::step <regex::backtrack::Bounded<regex::input::CharInput>>::step Line | Count | Source | 198 | 572k | fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool { | 199 | | use crate::prog::Inst::*; | 200 | | loop { | 201 | | // This loop is an optimization to avoid constantly pushing/popping | 202 | | // from the stack. Namely, if we're pushing a job only to run it | 203 | | // next, avoid the push and just mutate `ip` (and possibly `at`) | 204 | | // in place. | 205 | 2.22M | if self.has_visited(ip, at) { | 206 | 10.8k | return false; | 207 | 2.21M | } | 208 | 2.21M | match self.prog[ip] { | 209 | 21.6k | Match(slot) => { | 210 | 21.6k | if slot < self.matches.len() { | 211 | 21.6k | self.matches[slot] = true; | 212 | 21.6k | } | 213 | 21.6k | return true; | 214 | | } | 215 | 190k | Save(ref inst) => { | 216 | 190k | if let Some(&old_pos) = self.slots.get(inst.slot) { | 217 | 190k | // If this path doesn't work out, then we save the old | 218 | 190k | // capture index (if one exists) in an alternate | 219 | 190k | // job. If the next path fails, then the alternate | 220 | 190k | // job is popped and the old capture index is restored. | 221 | 190k | self.m.jobs.push(Job::SaveRestore { | 222 | 190k | slot: inst.slot, | 223 | 190k | old_pos: old_pos, | 224 | 190k | }); | 225 | 190k | self.slots[inst.slot] = Some(at.pos()); | 226 | 190k | } | 227 | 190k | ip = inst.goto; | 228 | | } | 229 | 799k | Split(ref inst) => { | 230 | 799k | self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at }); | 231 | 799k | ip = inst.goto1; | 232 | 799k | } | 233 | 107k | EmptyLook(ref inst) => { | 234 | 107k | if self.input.is_empty_match(at, inst) { | 235 | 79.1k | ip = inst.goto; | 236 | 79.1k | } else { | 237 | 28.5k | return false; | 238 | | } | 239 | | } | 240 | 471k | Char(ref inst) => { | 241 | 471k | if inst.c == at.char() { | 242 | 247k | ip = inst.goto; | 243 | 247k | at = self.input.at(at.next_pos()); | 244 | 247k | } else { | 245 | 223k | return false; | 246 | | } | 247 | | } | 248 | 623k | Ranges(ref inst) => { | 249 | 623k | if inst.matches(at.char()) { | 250 | 335k | ip = inst.goto; | 251 | 335k | at = self.input.at(at.next_pos()); | 252 | 335k | } else { | 253 | 288k | return false; | 254 | | } | 255 | | } | 256 | 0 | Bytes(ref inst) => { | 257 | 0 | if let Some(b) = at.byte() { | 258 | 0 | if inst.matches(b) { | 259 | 0 | ip = inst.goto; | 260 | 0 | at = self.input.at(at.next_pos()); | 261 | 0 | continue; | 262 | 0 | } | 263 | 0 | } | 264 | 0 | return false; | 265 | | } | 266 | | } | 267 | | } | 268 | 572k | } |
|
269 | | |
270 | 2.52M | fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool { |
271 | 2.52M | let k = ip * (self.input.len() + 1) + at.pos(); |
272 | 2.52M | let k1 = k / BIT_SIZE; |
273 | 2.52M | let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1))); |
274 | 2.52M | if self.m.visited[k1] & k2 == 0 { |
275 | 2.51M | self.m.visited[k1] |= k2; |
276 | 2.51M | false |
277 | | } else { |
278 | 11.6k | true |
279 | | } |
280 | 2.52M | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::has_visited <regex::backtrack::Bounded<regex::input::CharInput>>::has_visited Line | Count | Source | 270 | 303k | fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool { | 271 | 303k | let k = ip * (self.input.len() + 1) + at.pos(); | 272 | 303k | let k1 = k / BIT_SIZE; | 273 | 303k | let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1))); | 274 | 303k | if self.m.visited[k1] & k2 == 0 { | 275 | 302k | self.m.visited[k1] |= k2; | 276 | 302k | false | 277 | | } else { | 278 | 745 | true | 279 | | } | 280 | 303k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::has_visited <regex::backtrack::Bounded<regex::input::CharInput>>::has_visited Line | Count | Source | 270 | 2.22M | fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool { | 271 | 2.22M | let k = ip * (self.input.len() + 1) + at.pos(); | 272 | 2.22M | let k1 = k / BIT_SIZE; | 273 | 2.22M | let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1))); | 274 | 2.22M | if self.m.visited[k1] & k2 == 0 { | 275 | 2.21M | self.m.visited[k1] |= k2; | 276 | 2.21M | false | 277 | | } else { | 278 | 10.8k | true | 279 | | } | 280 | 2.22M | } |
|
281 | | } |
282 | | |
283 | 2.52M | fn usize_to_u32(n: usize) -> u32 { |
284 | 2.52M | if (n as u64) > (::std::u32::MAX as u64) { |
285 | 0 | panic!("BUG: {} is too big to fit into u32", n) |
286 | 2.52M | } |
287 | 2.52M | n as u32 |
288 | 2.52M | } regex::backtrack::usize_to_u32 Line | Count | Source | 283 | 303k | fn usize_to_u32(n: usize) -> u32 { | 284 | 303k | if (n as u64) > (::std::u32::MAX as u64) { | 285 | 0 | panic!("BUG: {} is too big to fit into u32", n) | 286 | 303k | } | 287 | 303k | n as u32 | 288 | 303k | } |
regex::backtrack::usize_to_u32 Line | Count | Source | 283 | 2.22M | fn usize_to_u32(n: usize) -> u32 { | 284 | 2.22M | if (n as u64) > (::std::u32::MAX as u64) { | 285 | 0 | panic!("BUG: {} is too big to fit into u32", n) | 286 | 2.22M | } | 287 | 2.22M | n as u32 | 288 | 2.22M | } |
|