| 1 | use crate::{hybrid::id::LazyStateIDError, nfa, util::search::Anchored}; |
| 2 | |
| 3 | /// An error that occurs when initial construction of a lazy DFA fails. |
| 4 | /// |
| 5 | /// A build error can occur when insufficient cache capacity is configured or |
| 6 | /// if something about the NFA is unsupported. (For example, if one attempts |
| 7 | /// to build a lazy DFA without heuristic Unicode support but with an NFA that |
| 8 | /// contains a Unicode word boundary.) |
| 9 | /// |
| 10 | /// This error does not provide many introspection capabilities. There are |
| 11 | /// generally only two things you can do with it: |
| 12 | /// |
| 13 | /// * Obtain a human readable message via its `std::fmt::Display` impl. |
| 14 | /// * Access an underlying |
| 15 | /// [`nfa::thompson::BuildError`](crate::nfa::thompson::BuildError) |
| 16 | /// type from its `source` method via the `std::error::Error` trait. This error |
| 17 | /// only occurs when using convenience routines for building a lazy DFA |
| 18 | /// directly from a pattern string. |
| 19 | /// |
| 20 | /// When the `std` feature is enabled, this implements the `std::error::Error` |
| 21 | /// trait. |
| 22 | #[derive (Clone, Debug)] |
| 23 | pub struct BuildError { |
| 24 | kind: BuildErrorKind, |
| 25 | } |
| 26 | |
| 27 | #[derive (Clone, Debug)] |
| 28 | enum BuildErrorKind { |
| 29 | NFA(nfa::thompson::BuildError), |
| 30 | InsufficientCacheCapacity { minimum: usize, given: usize }, |
| 31 | InsufficientStateIDCapacity { err: LazyStateIDError }, |
| 32 | Unsupported(&'static str), |
| 33 | } |
| 34 | |
| 35 | impl BuildError { |
| 36 | pub(crate) fn nfa(err: nfa::thompson::BuildError) -> BuildError { |
| 37 | BuildError { kind: BuildErrorKind::NFA(err) } |
| 38 | } |
| 39 | |
| 40 | pub(crate) fn insufficient_cache_capacity( |
| 41 | minimum: usize, |
| 42 | given: usize, |
| 43 | ) -> BuildError { |
| 44 | BuildError { |
| 45 | kind: BuildErrorKind::InsufficientCacheCapacity { minimum, given }, |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | pub(crate) fn insufficient_state_id_capacity( |
| 50 | err: LazyStateIDError, |
| 51 | ) -> BuildError { |
| 52 | BuildError { |
| 53 | kind: BuildErrorKind::InsufficientStateIDCapacity { err }, |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | pub(crate) fn unsupported_dfa_word_boundary_unicode() -> BuildError { |
| 58 | let msg = "cannot build lazy DFAs for regexes with Unicode word \ |
| 59 | boundaries; switch to ASCII word boundaries, or \ |
| 60 | heuristically enable Unicode word boundaries or use a \ |
| 61 | different regex engine" ; |
| 62 | BuildError { kind: BuildErrorKind::Unsupported(msg) } |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | #[cfg (feature = "std" )] |
| 67 | impl std::error::Error for BuildError { |
| 68 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 69 | match self.kind { |
| 70 | BuildErrorKind::NFA(ref err: &BuildError) => Some(err), |
| 71 | _ => None, |
| 72 | } |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | impl core::fmt::Display for BuildError { |
| 77 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 78 | match self.kind { |
| 79 | BuildErrorKind::NFA(_) => write!(f, "error building NFA" ), |
| 80 | BuildErrorKind::InsufficientCacheCapacity { minimum: usize, given: usize } => { |
| 81 | write!( |
| 82 | f, |
| 83 | "given cache capacity ( {}) is smaller than \ |
| 84 | minimum required ( {})" , |
| 85 | given, minimum, |
| 86 | ) |
| 87 | } |
| 88 | BuildErrorKind::InsufficientStateIDCapacity { ref err: &LazyStateIDError } => { |
| 89 | err.fmt(f) |
| 90 | } |
| 91 | BuildErrorKind::Unsupported(ref msg: &&'static str) => { |
| 92 | write!(f, "unsupported regex feature for DFAs: {}" , msg) |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | /// An error that can occur when computing the start state for a search. |
| 99 | /// |
| 100 | /// Computing a start state can fail for a few reasons, either |
| 101 | /// based on incorrect configuration or even based on whether |
| 102 | /// the look-behind byte triggers a quit state. Typically |
| 103 | /// one does not need to handle this error if you're using |
| 104 | /// [`DFA::start_state_forward`](crate::hybrid::dfa::DFA::start_state_forward) |
| 105 | /// (or its reverse counterpart), as that routine automatically converts |
| 106 | /// `StartError` to a [`MatchError`](crate::MatchError) for you. |
| 107 | /// |
| 108 | /// This error may be returned by the |
| 109 | /// [`DFA::start_state`](crate::hybrid::dfa::DFA::start_state) routine. |
| 110 | /// |
| 111 | /// This error implements the `std::error::Error` trait when the `std` feature |
| 112 | /// is enabled. |
| 113 | /// |
| 114 | /// This error is marked as non-exhaustive. New variants may be added in a |
| 115 | /// semver compatible release. |
| 116 | #[non_exhaustive ] |
| 117 | #[derive (Clone, Debug)] |
| 118 | pub enum StartError { |
| 119 | /// An error that occurs when cache inefficiency has dropped below the |
| 120 | /// configured heuristic thresholds. |
| 121 | Cache { |
| 122 | /// The underlying cache error that occurred. |
| 123 | err: CacheError, |
| 124 | }, |
| 125 | /// An error that occurs when a starting configuration's look-behind byte |
| 126 | /// is in this DFA's quit set. |
| 127 | Quit { |
| 128 | /// The quit byte that was found. |
| 129 | byte: u8, |
| 130 | }, |
| 131 | /// An error that occurs when the caller requests an anchored mode that |
| 132 | /// isn't supported by the DFA. |
| 133 | UnsupportedAnchored { |
| 134 | /// The anchored mode given that is unsupported. |
| 135 | mode: Anchored, |
| 136 | }, |
| 137 | } |
| 138 | |
| 139 | impl StartError { |
| 140 | pub(crate) fn cache(err: CacheError) -> StartError { |
| 141 | StartError::Cache { err } |
| 142 | } |
| 143 | |
| 144 | pub(crate) fn quit(byte: u8) -> StartError { |
| 145 | StartError::Quit { byte } |
| 146 | } |
| 147 | |
| 148 | pub(crate) fn unsupported_anchored(mode: Anchored) -> StartError { |
| 149 | StartError::UnsupportedAnchored { mode } |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | #[cfg (feature = "std" )] |
| 154 | impl std::error::Error for StartError { |
| 155 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 156 | match *self { |
| 157 | StartError::Cache { ref err: &CacheError } => Some(err), |
| 158 | _ => None, |
| 159 | } |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | impl core::fmt::Display for StartError { |
| 164 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 165 | match *self { |
| 166 | StartError::Cache { .. } => write!( |
| 167 | f, |
| 168 | "error computing start state because of cache inefficiency" |
| 169 | ), |
| 170 | StartError::Quit { byte } => write!( |
| 171 | f, |
| 172 | "error computing start state because the look-behind byte \ |
| 173 | {:?} triggered a quit state" , |
| 174 | crate::util::escape::DebugByte(byte), |
| 175 | ), |
| 176 | StartError::UnsupportedAnchored { mode: Anchored::Yes } => { |
| 177 | write!( |
| 178 | f, |
| 179 | "error computing start state because \ |
| 180 | anchored searches are not supported or enabled" |
| 181 | ) |
| 182 | } |
| 183 | StartError::UnsupportedAnchored { mode: Anchored::No } => { |
| 184 | write!( |
| 185 | f, |
| 186 | "error computing start state because \ |
| 187 | unanchored searches are not supported or enabled" |
| 188 | ) |
| 189 | } |
| 190 | StartError::UnsupportedAnchored { |
| 191 | mode: Anchored::Pattern(pid), |
| 192 | } => { |
| 193 | write!( |
| 194 | f, |
| 195 | "error computing start state because \ |
| 196 | anchored searches for a specific pattern ( {}) \ |
| 197 | are not supported or enabled" , |
| 198 | pid.as_usize(), |
| 199 | ) |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | /// An error that occurs when cache usage has become inefficient. |
| 206 | /// |
| 207 | /// One of the weaknesses of a lazy DFA is that it may need to clear its |
| 208 | /// cache repeatedly if it's not big enough. If this happens too much, then it |
| 209 | /// can slow searching down significantly. A mitigation to this is to use |
| 210 | /// heuristics to detect whether the cache is being used efficiently or not. |
| 211 | /// If not, then a lazy DFA can return a `CacheError`. |
| 212 | /// |
| 213 | /// The default configuration of a lazy DFA in this crate is |
| 214 | /// set such that a `CacheError` will never occur. Instead, |
| 215 | /// callers must opt into this behavior with settings like |
| 216 | /// [`dfa::Config::minimum_cache_clear_count`](crate::hybrid::dfa::Config::minimum_cache_clear_count) |
| 217 | /// and |
| 218 | /// [`dfa::Config::minimum_bytes_per_state`](crate::hybrid::dfa::Config::minimum_bytes_per_state). |
| 219 | /// |
| 220 | /// When the `std` feature is enabled, this implements the `std::error::Error` |
| 221 | /// trait. |
| 222 | #[derive (Clone, Debug)] |
| 223 | pub struct CacheError(()); |
| 224 | |
| 225 | impl CacheError { |
| 226 | pub(crate) fn too_many_cache_clears() -> CacheError { |
| 227 | CacheError(()) |
| 228 | } |
| 229 | |
| 230 | pub(crate) fn bad_efficiency() -> CacheError { |
| 231 | CacheError(()) |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | #[cfg (feature = "std" )] |
| 236 | impl std::error::Error for CacheError {} |
| 237 | |
| 238 | impl core::fmt::Display for CacheError { |
| 239 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 240 | write!(f, "lazy DFA cache has been cleared too many times" ) |
| 241 | } |
| 242 | } |
| 243 | |