/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 | 139k | 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 | 139k | let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4; |
39 | 139k | size <= MAX_SIZE_BYTES |
40 | 139k | } regex::backtrack::should_exec Line | Count | Source | 31 | 61.9k | 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 | 61.9k | let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4; | 39 | 61.9k | size <= MAX_SIZE_BYTES | 40 | 61.9k | } |
regex::backtrack::should_exec Line | Count | Source | 31 | 77.3k | 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 | 77.3k | let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4; | 39 | 77.3k | size <= MAX_SIZE_BYTES | 40 | 77.3k | } |
|
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 | 15 | pub fn new(_prog: &Program) -> Self { |
63 | 15 | Cache { jobs: vec![], visited: vec![] } |
64 | 15 | } <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 | 6 | pub fn new(_prog: &Program) -> Self { | 63 | 6 | Cache { jobs: vec![], visited: vec![] } | 64 | 6 | } |
|
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 | 139k | pub fn exec( |
85 | 139k | prog: &'r Program, |
86 | 139k | cache: &ProgramCache, |
87 | 139k | matches: &'m mut [bool], |
88 | 139k | slots: &'s mut [Slot], |
89 | 139k | input: I, |
90 | 139k | start: usize, |
91 | 139k | end: usize, |
92 | 139k | ) -> bool { |
93 | 139k | let mut cache = cache.borrow_mut(); |
94 | 139k | let cache = &mut cache.backtrack; |
95 | 139k | let start = input.at(start); |
96 | 139k | let mut b = Bounded { |
97 | 139k | prog: prog, |
98 | 139k | input: input, |
99 | 139k | matches: matches, |
100 | 139k | slots: slots, |
101 | 139k | m: cache, |
102 | 139k | }; |
103 | 139k | b.exec_(start, end) |
104 | 139k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec <regex::backtrack::Bounded<regex::input::CharInput>>::exec Line | Count | Source | 84 | 61.9k | pub fn exec( | 85 | 61.9k | prog: &'r Program, | 86 | 61.9k | cache: &ProgramCache, | 87 | 61.9k | matches: &'m mut [bool], | 88 | 61.9k | slots: &'s mut [Slot], | 89 | 61.9k | input: I, | 90 | 61.9k | start: usize, | 91 | 61.9k | end: usize, | 92 | 61.9k | ) -> bool { | 93 | 61.9k | let mut cache = cache.borrow_mut(); | 94 | 61.9k | let cache = &mut cache.backtrack; | 95 | 61.9k | let start = input.at(start); | 96 | 61.9k | let mut b = Bounded { | 97 | 61.9k | prog: prog, | 98 | 61.9k | input: input, | 99 | 61.9k | matches: matches, | 100 | 61.9k | slots: slots, | 101 | 61.9k | m: cache, | 102 | 61.9k | }; | 103 | 61.9k | b.exec_(start, end) | 104 | 61.9k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec <regex::backtrack::Bounded<regex::input::CharInput>>::exec Line | Count | Source | 84 | 77.3k | pub fn exec( | 85 | 77.3k | prog: &'r Program, | 86 | 77.3k | cache: &ProgramCache, | 87 | 77.3k | matches: &'m mut [bool], | 88 | 77.3k | slots: &'s mut [Slot], | 89 | 77.3k | input: I, | 90 | 77.3k | start: usize, | 91 | 77.3k | end: usize, | 92 | 77.3k | ) -> bool { | 93 | 77.3k | let mut cache = cache.borrow_mut(); | 94 | 77.3k | let cache = &mut cache.backtrack; | 95 | 77.3k | let start = input.at(start); | 96 | 77.3k | let mut b = Bounded { | 97 | 77.3k | prog: prog, | 98 | 77.3k | input: input, | 99 | 77.3k | matches: matches, | 100 | 77.3k | slots: slots, | 101 | 77.3k | m: cache, | 102 | 77.3k | }; | 103 | 77.3k | b.exec_(start, end) | 104 | 77.3k | } |
|
105 | | |
106 | | /// Clears the cache such that the backtracking engine can be executed |
107 | | /// on some input of fixed length. |
108 | 139k | fn clear(&mut self) { |
109 | | // Reset the job memory so that we start fresh. |
110 | 139k | 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 | 139k | let visited_len = |
123 | 139k | (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1) |
124 | 139k | / BIT_SIZE; |
125 | 139k | self.m.visited.truncate(visited_len); |
126 | 6.40M | for v in &mut self.m.visited { |
127 | 6.26M | *v = 0; |
128 | 6.26M | } |
129 | 139k | if visited_len > self.m.visited.len() { |
130 | 38.7k | let len = self.m.visited.len(); |
131 | 38.7k | self.m.visited.reserve_exact(visited_len - len); |
132 | 1.74M | for _ in 0..(visited_len - len) { |
133 | 1.74M | self.m.visited.push(0); |
134 | 1.74M | } |
135 | 100k | } |
136 | 139k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::clear <regex::backtrack::Bounded<regex::input::CharInput>>::clear Line | Count | Source | 108 | 61.9k | fn clear(&mut self) { | 109 | | // Reset the job memory so that we start fresh. | 110 | 61.9k | 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 | 61.9k | let visited_len = | 123 | 61.9k | (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1) | 124 | 61.9k | / BIT_SIZE; | 125 | 61.9k | self.m.visited.truncate(visited_len); | 126 | 3.37M | for v in &mut self.m.visited { | 127 | 3.31M | *v = 0; | 128 | 3.31M | } | 129 | 61.9k | if visited_len > self.m.visited.len() { | 130 | 18.9k | let len = self.m.visited.len(); | 131 | 18.9k | self.m.visited.reserve_exact(visited_len - len); | 132 | 871k | for _ in 0..(visited_len - len) { | 133 | 871k | self.m.visited.push(0); | 134 | 871k | } | 135 | 43.0k | } | 136 | 61.9k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::clear <regex::backtrack::Bounded<regex::input::CharInput>>::clear Line | Count | Source | 108 | 77.3k | fn clear(&mut self) { | 109 | | // Reset the job memory so that we start fresh. | 110 | 77.3k | 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 | 77.3k | let visited_len = | 123 | 77.3k | (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1) | 124 | 77.3k | / BIT_SIZE; | 125 | 77.3k | self.m.visited.truncate(visited_len); | 126 | 3.03M | for v in &mut self.m.visited { | 127 | 2.95M | *v = 0; | 128 | 2.95M | } | 129 | 77.3k | if visited_len > self.m.visited.len() { | 130 | 19.7k | let len = self.m.visited.len(); | 131 | 19.7k | self.m.visited.reserve_exact(visited_len - len); | 132 | 874k | for _ in 0..(visited_len - len) { | 133 | 874k | self.m.visited.push(0); | 134 | 874k | } | 135 | 57.5k | } | 136 | 77.3k | } |
|
137 | | |
138 | | /// Start backtracking at the given position in the input, but also look |
139 | | /// for literal prefixes. |
140 | 139k | fn exec_(&mut self, mut at: InputAt, end: usize) -> bool { |
141 | 139k | 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 | 139k | if self.prog.is_anchored_start { |
145 | 139k | 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 | 139k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec_ <regex::backtrack::Bounded<regex::input::CharInput>>::exec_ Line | Count | Source | 140 | 61.9k | fn exec_(&mut self, mut at: InputAt, end: usize) -> bool { | 141 | 61.9k | 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 | 61.9k | if self.prog.is_anchored_start { | 145 | 61.9k | 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 | 61.9k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec_ <regex::backtrack::Bounded<regex::input::CharInput>>::exec_ Line | Count | Source | 140 | 77.3k | fn exec_(&mut self, mut at: InputAt, end: usize) -> bool { | 141 | 77.3k | 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 | 77.3k | if self.prog.is_anchored_start { | 145 | 77.3k | 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 | 77.3k | } |
|
166 | | |
167 | | /// The main backtracking loop starting at the given input position. |
168 | 139k | 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 | 139k | let mut matched = false; |
174 | 139k | self.m.jobs.push(Job::Inst { ip: 0, at: start }); |
175 | 1.61M | while let Some(job) = self.m.jobs.pop() { |
176 | 1.52M | match job { |
177 | 1.34M | Job::Inst { ip, at } => { |
178 | 1.34M | 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 | 51.1k | if self.prog.matches.len() == 1 { |
183 | 51.1k | return true; |
184 | 0 | } |
185 | 0 | matched = true; |
186 | 1.29M | } |
187 | | } |
188 | 178k | Job::SaveRestore { slot, old_pos } => { |
189 | 178k | if slot < self.slots.len() { |
190 | 178k | self.slots[slot] = old_pos; |
191 | 178k | } |
192 | | } |
193 | | } |
194 | | } |
195 | 88.1k | matched |
196 | 139k | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::backtrack <regex::backtrack::Bounded<regex::input::CharInput>>::backtrack Line | Count | Source | 168 | 61.9k | 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 | 61.9k | let mut matched = false; | 174 | 61.9k | self.m.jobs.push(Job::Inst { ip: 0, at: start }); | 175 | 865k | while let Some(job) = self.m.jobs.pop() { | 176 | 826k | match job { | 177 | 720k | Job::Inst { ip, at } => { | 178 | 720k | 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 | 22.6k | if self.prog.matches.len() == 1 { | 183 | 22.6k | return true; | 184 | 0 | } | 185 | 0 | matched = true; | 186 | 697k | } | 187 | | } | 188 | 106k | Job::SaveRestore { slot, old_pos } => { | 189 | 106k | if slot < self.slots.len() { | 190 | 106k | self.slots[slot] = old_pos; | 191 | 106k | } | 192 | | } | 193 | | } | 194 | | } | 195 | 39.3k | matched | 196 | 61.9k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::backtrack <regex::backtrack::Bounded<regex::input::CharInput>>::backtrack Line | Count | Source | 168 | 77.3k | 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 | 77.3k | let mut matched = false; | 174 | 77.3k | self.m.jobs.push(Job::Inst { ip: 0, at: start }); | 175 | 745k | while let Some(job) = self.m.jobs.pop() { | 176 | 697k | match job { | 177 | 624k | Job::Inst { ip, at } => { | 178 | 624k | 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 | 28.5k | if self.prog.matches.len() == 1 { | 183 | 28.5k | return true; | 184 | 0 | } | 185 | 0 | matched = true; | 186 | 596k | } | 187 | | } | 188 | 72.1k | Job::SaveRestore { slot, old_pos } => { | 189 | 72.1k | if slot < self.slots.len() { | 190 | 72.1k | self.slots[slot] = old_pos; | 191 | 72.1k | } | 192 | | } | 193 | | } | 194 | | } | 195 | 48.7k | matched | 196 | 77.3k | } |
|
197 | | |
198 | 1.34M | 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 | 4.86M | if self.has_visited(ip, at) { |
206 | 27.8k | return false; |
207 | 4.84M | } |
208 | 4.84M | match self.prog[ip] { |
209 | 51.1k | Match(slot) => { |
210 | 51.1k | if slot < self.matches.len() { |
211 | 51.1k | self.matches[slot] = true; |
212 | 51.1k | } |
213 | 51.1k | return true; |
214 | | } |
215 | 431k | Save(ref inst) => { |
216 | 431k | if let Some(&old_pos) = self.slots.get(inst.slot) { |
217 | 431k | // If this path doesn't work out, then we save the old |
218 | 431k | // capture index (if one exists) in an alternate |
219 | 431k | // job. If the next path fails, then the alternate |
220 | 431k | // job is popped and the old capture index is restored. |
221 | 431k | self.m.jobs.push(Job::SaveRestore { |
222 | 431k | slot: inst.slot, |
223 | 431k | old_pos: old_pos, |
224 | 431k | }); |
225 | 431k | self.slots[inst.slot] = Some(at.pos()); |
226 | 431k | } |
227 | 431k | ip = inst.goto; |
228 | | } |
229 | 1.71M | Split(ref inst) => { |
230 | 1.71M | self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at }); |
231 | 1.71M | ip = inst.goto1; |
232 | 1.71M | } |
233 | 255k | EmptyLook(ref inst) => { |
234 | 255k | if self.input.is_empty_match(at, inst) { |
235 | 190k | ip = inst.goto; |
236 | 190k | } else { |
237 | 65.3k | return false; |
238 | | } |
239 | | } |
240 | 1.08M | Char(ref inst) => { |
241 | 1.08M | if inst.c == at.char() { |
242 | 548k | ip = inst.goto; |
243 | 548k | at = self.input.at(at.next_pos()); |
244 | 548k | } else { |
245 | 538k | return false; |
246 | | } |
247 | | } |
248 | 1.30M | Ranges(ref inst) => { |
249 | 1.30M | if inst.matches(at.char()) { |
250 | 639k | ip = inst.goto; |
251 | 639k | at = self.input.at(at.next_pos()); |
252 | 639k | } else { |
253 | 662k | 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 | 1.34M | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::step <regex::backtrack::Bounded<regex::input::CharInput>>::step Line | Count | Source | 198 | 720k | 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.57M | if self.has_visited(ip, at) { | 206 | 20.5k | return false; | 207 | 2.55M | } | 208 | 2.55M | match self.prog[ip] { | 209 | 22.6k | Match(slot) => { | 210 | 22.6k | if slot < self.matches.len() { | 211 | 22.6k | self.matches[slot] = true; | 212 | 22.6k | } | 213 | 22.6k | return true; | 214 | | } | 215 | 223k | Save(ref inst) => { | 216 | 223k | if let Some(&old_pos) = self.slots.get(inst.slot) { | 217 | 223k | // If this path doesn't work out, then we save the old | 218 | 223k | // capture index (if one exists) in an alternate | 219 | 223k | // job. If the next path fails, then the alternate | 220 | 223k | // job is popped and the old capture index is restored. | 221 | 223k | self.m.jobs.push(Job::SaveRestore { | 222 | 223k | slot: inst.slot, | 223 | 223k | old_pos: old_pos, | 224 | 223k | }); | 225 | 223k | self.slots[inst.slot] = Some(at.pos()); | 226 | 223k | } | 227 | 223k | ip = inst.goto; | 228 | | } | 229 | 931k | Split(ref inst) => { | 230 | 931k | self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at }); | 231 | 931k | ip = inst.goto1; | 232 | 931k | } | 233 | 140k | EmptyLook(ref inst) => { | 234 | 140k | if self.input.is_empty_match(at, inst) { | 235 | 84.5k | ip = inst.goto; | 236 | 84.5k | } else { | 237 | 56.2k | return false; | 238 | | } | 239 | | } | 240 | 526k | Char(ref inst) => { | 241 | 526k | if inst.c == at.char() { | 242 | 254k | ip = inst.goto; | 243 | 254k | at = self.input.at(at.next_pos()); | 244 | 254k | } else { | 245 | 272k | return false; | 246 | | } | 247 | | } | 248 | 713k | Ranges(ref inst) => { | 249 | 713k | if inst.matches(at.char()) { | 250 | 364k | ip = inst.goto; | 251 | 364k | at = self.input.at(at.next_pos()); | 252 | 364k | } else { | 253 | 348k | 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 | 720k | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::step <regex::backtrack::Bounded<regex::input::CharInput>>::step Line | Count | Source | 198 | 624k | 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.28M | if self.has_visited(ip, at) { | 206 | 7.31k | return false; | 207 | 2.28M | } | 208 | 2.28M | match self.prog[ip] { | 209 | 28.5k | Match(slot) => { | 210 | 28.5k | if slot < self.matches.len() { | 211 | 28.5k | self.matches[slot] = true; | 212 | 28.5k | } | 213 | 28.5k | return true; | 214 | | } | 215 | 207k | Save(ref inst) => { | 216 | 207k | if let Some(&old_pos) = self.slots.get(inst.slot) { | 217 | 207k | // If this path doesn't work out, then we save the old | 218 | 207k | // capture index (if one exists) in an alternate | 219 | 207k | // job. If the next path fails, then the alternate | 220 | 207k | // job is popped and the old capture index is restored. | 221 | 207k | self.m.jobs.push(Job::SaveRestore { | 222 | 207k | slot: inst.slot, | 223 | 207k | old_pos: old_pos, | 224 | 207k | }); | 225 | 207k | self.slots[inst.slot] = Some(at.pos()); | 226 | 207k | } | 227 | 207k | ip = inst.goto; | 228 | | } | 229 | 782k | Split(ref inst) => { | 230 | 782k | self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at }); | 231 | 782k | ip = inst.goto1; | 232 | 782k | } | 233 | 115k | EmptyLook(ref inst) => { | 234 | 115k | if self.input.is_empty_match(at, inst) { | 235 | 105k | ip = inst.goto; | 236 | 105k | } else { | 237 | 9.11k | return false; | 238 | | } | 239 | | } | 240 | 560k | Char(ref inst) => { | 241 | 560k | if inst.c == at.char() { | 242 | 293k | ip = inst.goto; | 243 | 293k | at = self.input.at(at.next_pos()); | 244 | 293k | } else { | 245 | 266k | return false; | 246 | | } | 247 | | } | 248 | 588k | Ranges(ref inst) => { | 249 | 588k | if inst.matches(at.char()) { | 250 | 274k | ip = inst.goto; | 251 | 274k | at = self.input.at(at.next_pos()); | 252 | 274k | } else { | 253 | 313k | 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 | 624k | } |
|
269 | | |
270 | 4.86M | fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool { |
271 | 4.86M | let k = ip * (self.input.len() + 1) + at.pos(); |
272 | 4.86M | let k1 = k / BIT_SIZE; |
273 | 4.86M | let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1))); |
274 | 4.86M | if self.m.visited[k1] & k2 == 0 { |
275 | 4.84M | self.m.visited[k1] |= k2; |
276 | 4.84M | false |
277 | | } else { |
278 | 27.8k | true |
279 | | } |
280 | 4.86M | } Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::has_visited <regex::backtrack::Bounded<regex::input::CharInput>>::has_visited Line | Count | Source | 270 | 2.57M | fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool { | 271 | 2.57M | let k = ip * (self.input.len() + 1) + at.pos(); | 272 | 2.57M | let k1 = k / BIT_SIZE; | 273 | 2.57M | let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1))); | 274 | 2.57M | if self.m.visited[k1] & k2 == 0 { | 275 | 2.55M | self.m.visited[k1] |= k2; | 276 | 2.55M | false | 277 | | } else { | 278 | 20.5k | true | 279 | | } | 280 | 2.57M | } |
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::has_visited <regex::backtrack::Bounded<regex::input::CharInput>>::has_visited Line | Count | Source | 270 | 2.28M | fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool { | 271 | 2.28M | let k = ip * (self.input.len() + 1) + at.pos(); | 272 | 2.28M | let k1 = k / BIT_SIZE; | 273 | 2.28M | let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1))); | 274 | 2.28M | if self.m.visited[k1] & k2 == 0 { | 275 | 2.28M | self.m.visited[k1] |= k2; | 276 | 2.28M | false | 277 | | } else { | 278 | 7.31k | true | 279 | | } | 280 | 2.28M | } |
|
281 | | } |
282 | | |
283 | 4.86M | fn usize_to_u32(n: usize) -> u32 { |
284 | 4.86M | if (n as u64) > (::std::u32::MAX as u64) { |
285 | 0 | panic!("BUG: {} is too big to fit into u32", n) |
286 | 4.86M | } |
287 | 4.86M | n as u32 |
288 | 4.86M | } regex::backtrack::usize_to_u32 Line | Count | Source | 283 | 2.57M | fn usize_to_u32(n: usize) -> u32 { | 284 | 2.57M | if (n as u64) > (::std::u32::MAX as u64) { | 285 | 0 | panic!("BUG: {} is too big to fit into u32", n) | 286 | 2.57M | } | 287 | 2.57M | n as u32 | 288 | 2.57M | } |
regex::backtrack::usize_to_u32 Line | Count | Source | 283 | 2.28M | fn usize_to_u32(n: usize) -> u32 { | 284 | 2.28M | if (n as u64) > (::std::u32::MAX as u64) { | 285 | 0 | panic!("BUG: {} is too big to fit into u32", n) | 286 | 2.28M | } | 287 | 2.28M | n as u32 | 288 | 2.28M | } |
|