vectorscan-async 0.0.4

Wrapper for the vectorscan C++ regex library.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
/* Copyright 2022-2024 Danny McClanahan */
/* SPDX-License-Identifier: BSD-3-Clause */

//! Errors returned by methods in this library.

use crate::hs;
#[cfg(feature = "compiler")]
use crate::matchers::ExpressionIndex;

use displaydoc::Display;
use thiserror::Error;

#[cfg(feature = "compiler")]
use std::{
  ffi::{CStr, NulError},
  fmt,
  os::raw::c_uint,
};

/// Native error code from the underlying vectorscan library.
#[derive(
  Debug,
  Display,
  Error,
  Copy,
  Clone,
  PartialEq,
  Eq,
  PartialOrd,
  Ord,
  Hash,
  num_enum::IntoPrimitive,
  num_enum::FromPrimitive,
)]
#[repr(i8)]
#[ignore_extra_doc_attributes]
pub enum VectorscanRuntimeError {
  /// A parameter passed to this function was invalid.
  ///
  /// This error is only returned in cases where the function can detect an
  /// invalid parameter -- it cannot be relied upon to detect (for example)
  /// pointers to freed memory or other invalid data.
  Invalid = hs::HS_INVALID,
  /// A memory allocation failed.
  NoMem = hs::HS_NOMEM,
  /// The engine was terminated by callback.
  ///
  /// This return value indicates that the target buffer was partially scanned,
  /// but that the callback function returned
  /// [`MatchResult::CeaseMatching`](crate::matchers::MatchResult::CeaseMatching)
  /// after a match was located.
  ScanTerminated = hs::HS_SCAN_TERMINATED,
  /// The pattern compiler failed, and the [`CompileError`] should be
  /// inspected for more detail.
  CompilerError = hs::HS_COMPILER_ERROR,
  /// The given database was built for a different version of Vectorscan.
  DbVersionError = hs::HS_DB_VERSION_ERROR,
  /// The given database was built for a different platform (i.e., CPU type).
  DbPlatformError = hs::HS_DB_PLATFORM_ERROR,
  /// The given database was built for a different mode of operation.
  ///
  /// This error is returned when streaming calls are used with a block or
  /// vectored database and vice versa.
  DbModeError = hs::HS_DB_MODE_ERROR,
  /// A parameter passed to this function was not correctly aligned.
  BadAlign = hs::HS_BAD_ALIGN,
  /// The memory allocator returned incorrectly aligned memory.
  ///
  /// The memory allocator (either [`libc::malloc()`] or the allocator set with
  /// [`crate::alloc::set_allocator()`]) did not
  /// correctly return memory suitably aligned for the largest representable
  /// data type on this platform.
  BadAlloc = hs::HS_BAD_ALLOC,
  /// The scratch region was already in use.
  ///
  /// This error is returned when Vectorscan is able to detect that the scratch
  /// region given is already in use by another Vectorscan API call.
  ///
  /// A separate scratch region, allocated with
  /// [`Scratch::setup_for_db()`](crate::state::Scratch::setup_for_db) or
  /// [`Scratch::try_clone()`](crate::state::Scratch::try_clone), is required
  /// for every concurrent caller of the Vectorscan API.
  ///
  /// For example, this error might be returned when
  /// [`scan_sync()`](crate::state::Scratch::scan_sync) has been
  /// called inside a callback delivered by a currently-executing
  /// [`scan_sync()`](crate::state::Scratch::scan_sync) call using the same
  /// scratch region.
  ///
  /// Note: Not all concurrent uses of scratch regions may be detected. This
  /// error is intended as a best-effort debugging tool, not a guarantee.
  ///
  /// Note: safe Rust code should never see this error. See [`crate::state`] for
  /// ways to manage scratch spaces.
  ScratchInUse = hs::HS_SCRATCH_IN_USE,
  /// Unsupported CPU architecture.
  ///
  /// This error is returned when Vectorscan is able to detect that the current
  /// system does not support the required instruction set.
  ///
  /// At a minimum, Vectorscan requires Supplemental Streaming SIMD Extensions 3
  /// (SSSE3).
  ArchError = hs::HS_ARCH_ERROR,
  /// Provided buffer was too small.
  ///
  /// This error indicates that there was insufficient space in the buffer. The
  /// call should be repeated with a larger provided buffer.
  ///
  /// Note: in this situation, it is normal for the amount of space required to
  /// be returned in the same manner as the used space would have been
  /// returned if the call was successful.
  ///
  /// This value is referenced internally in
  /// [`LiveStream::compress()`](crate::stream::LiveStream) when requesting the
  /// amount of memory to allocate for a compressed stream. Users of this
  /// library should never see this error when using the
  /// [`CompressReserveBehavior`](crate::stream::CompressReserveBehavior)
  /// interface.
  InsufficientSpace = hs::HS_INSUFFICIENT_SPACE,
  /// Unexpected internal error.
  ///
  /// This error indicates that there was unexpected matching behaviors. This
  /// could be related to invalid usage of stream and scratch space or invalid
  /// memory operations by users.
  #[num_enum(default)]
  UnknownError = hs::HS_UNKNOWN_ERROR,
}

impl VectorscanRuntimeError {
  pub(crate) fn from_native(x: hs::hs_error_t) -> Result<(), Self> {
    static_assertions::const_assert_eq!(0, hs::HS_SUCCESS);
    if x == 0 {
      Ok(())
    } else {
      let s: Self = (x as i8).into();
      Err(s)
    }
  }

  #[cfg(feature = "compiler")]
  pub(crate) fn copy_from_native_compile_error(
    x: hs::hs_error_t,
    c: *mut hs::hs_compile_error,
  ) -> Result<(), VectorscanCompileError> {
    match Self::from_native(x) {
      Ok(()) => Ok(()),
      Err(Self::CompilerError) => {
        let e = CompileError::copy_from_native(unsafe { &mut *c }).unwrap();
        Err(VectorscanCompileError::Compile(e))
      },
      Err(e) => Err(e.into()),
    }
  }
}

/// Error details returned by the pattern compiler.
///
/// This is returned by the compile calls
/// ([`Database::compile()`](crate::database::Database::compile) and
/// [`Database::compile_multi()`](crate::database::Database::compile_multi)) on
/// failure. The caller may inspect the values returned in this type to
/// determine the cause of failure.
#[cfg(feature = "compiler")]
#[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
#[derive(Debug, Error)]
pub struct CompileError {
  /// A human-readable error message describing the error.
  ///
  /// # Common Errors
  /// Common errors generated during the compile process include:
  ///
  /// - *Invalid parameter:* An invalid argument was specified in the compile
  ///   call.
  ///
  /// - *Unrecognised flag:* An unrecognised value was passed in the flags
  ///   argument.
  ///
  /// - *Pattern matches empty buffer:* By default, Vectorscan only supports
  ///   patterns that will *always* consume at least one byte of input. Patterns
  ///   that do not have this property (such as `/(abc)?/`) will produce this
  ///   error unless the [`Flags::ALLOWEMPTY`](crate::flags::Flags::ALLOWEMPTY)
  ///   flag is supplied. Note that such patterns will produce a match for
  ///   *every* byte when scanned.
  ///
  /// - *Embedded anchors not supported:* Vectorscan only supports the use of
  ///   anchor meta-characters (such as `^` and `$`) in patterns where they
  ///   could *only* match at the start or end of a buffer. A pattern containing
  ///   an embedded anchor, such as `/abc^def/`, can never match, as there is no
  ///   way for `abc` to precede the start of the data stream.
  ///
  /// - *Bounded repeat is too large:* The pattern contains a repeated construct
  ///   with very large finite bounds.
  ///
  /// - *Unsupported component type:* An unsupported PCRE construct was used in
  ///   the pattern. Consider using [`chimera`](crate::expression::chimera) for
  ///   full PCRE support.
  ///
  /// - *Unable to generate bytecode:* This error indicates that Vectorscan was
  ///   unable to compile a pattern that is syntactically valid. The most common
  ///   cause is a pattern that is very long and complex or contains a large
  ///   repeated subpattern.
  ///
  /// - *Unable to allocate memory:* The library was unable to allocate
  ///   temporary storage used during compilation time.
  ///
  /// - *Allocator returned misaligned memory:* The memory allocator (either
  ///   [`libc::malloc()`] or the allocator set with
  ///   [`set_db_allocator()`](crate::alloc::set_db_allocator)) did not
  ///   correctly return memory suitably aligned for the largest representable
  ///   data type on this platform.
  ///
  /// - *Internal error:* An unexpected error occurred: if this error is
  ///   reported, please contact the Vectorscan team with a description of the
  ///   situation.
  pub message: String,
  /// The zero-based number of the expression that caused the error (if this
  /// can be determined). For a database with a single expression, this value
  /// will be `0`:
  ///
  ///```
  /// # fn main() -> Result<(), vectorscan::error::VectorscanError> {
  /// use vectorscan::{expression::*, error::*, matchers::*, flags::*};
  ///
  /// let expr: Expression = "as(df".parse()?;
  /// let index = match expr.compile(Flags::default(), Mode::BLOCK) {
  ///   Err(VectorscanCompileError::Compile(CompileError { expression, .. })) => expression,
  ///   _ => unreachable!(),
  /// };
  /// assert_eq!(index, Some(ExpressionIndex(0)));
  /// # Ok(())
  /// # }
  /// ```
  ///
  /// Note that while this uses the same [`ExpressionIndex`] type as in
  /// [`Match`](crate::matchers::Match), the value is *not*
  /// calculated from any [`ExprId`](crate::expression::ExprId) instances
  /// provided to
  /// [`ExpressionSet::with_ids()`](crate::expression::ExpressionSet::with_ids),
  /// but instead just from the expression's index in the set:
  ///
  ///```
  /// # fn main() -> Result<(), vectorscan::error::VectorscanError> {
  /// use vectorscan::{expression::*, error::*, matchers::*, flags::*};
  ///
  /// let e1: Expression = "aa".parse()?;
  /// let e2: Expression = "as(df".parse()?;
  /// let set = ExpressionSet::from_exprs([&e1, &e2]).with_ids([ExprId(2), ExprId(3)]);
  /// let index = match set.compile(Mode::BLOCK) {
  ///   Err(VectorscanCompileError::Compile(CompileError { expression, .. })) => expression,
  ///   _ => unreachable!(),
  /// };
  /// assert_eq!(index, Some(ExpressionIndex(1)));
  /// # Ok(())
  /// # }
  /// ```
  ///
  /// If the error is not specific to an expression, then this value will be
  /// [`None`]:
  ///```
  /// // Using vectorscan::alloc requires the "alloc" feature.
  /// #[cfg(feature = "alloc")]
  /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
  ///   use vectorscan::{expression::*, error::*, flags::*, alloc::*};
  ///   use std::{alloc::{GlobalAlloc, Layout}, ptr};
  ///
  ///   // Create a broken allocator:
  ///   struct S;
  ///   unsafe impl GlobalAlloc for S {
  ///     unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { ptr::null_mut() }
  ///     unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
  ///   }
  ///   // Set it as the db compile allocator:
  ///   assert!(set_db_allocator(LayoutTracker::new(S.into())).unwrap().is_none());
  ///
  ///   let expr: Expression = "a".parse()?;
  ///   let CompileError { message, expression } = match expr.compile(Flags::default(), Mode::BLOCK) {
  ///     Err(VectorscanCompileError::Compile(err)) => err,
  ///     _ => unreachable!(),
  ///   };
  ///   assert_eq!(expression, None);
  ///   assert_eq!(&message, "Could not allocate memory for bytecode.");
  ///   Ok(())
  /// }
  /// # #[cfg(not(feature = "alloc"))]
  /// # fn main() {}
  /// ```
  pub expression: Option<ExpressionIndex>,
}

#[cfg(feature = "compiler")]
impl fmt::Display for CompileError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(
      f,
      "compile error(@{:?}): {}",
      &self.expression, &self.message
    )
  }
}

#[cfg(feature = "compiler")]
impl CompileError {
  pub(crate) fn copy_from_native(
    x: &mut hs::hs_compile_error,
  ) -> Result<Self, VectorscanRuntimeError> {
    let hs::hs_compile_error {
      message,
      expression,
    } = x;
    assert!(!message.is_null());
    let ret = Self {
      message: unsafe { CStr::from_ptr(*message) }
        .to_string_lossy()
        .to_string(),
      expression: if *expression < 0 {
        None
      } else {
        Some(ExpressionIndex(*expression as c_uint))
      },
    };
    VectorscanRuntimeError::from_native(unsafe { hs::hs_free_compile_error(x) })?;
    Ok(ret)
  }
}

/// Wrapper for errors returned when parsing or compiling expressions.
#[cfg(feature = "compiler")]
#[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
#[derive(Debug, Display, Error)]
pub enum VectorscanCompileError {
  /// non-compilation error: {0}
  NonCompile(#[from] VectorscanRuntimeError),
  /// pattern compilation error: {0}
  Compile(#[from] CompileError),
  /// null byte in expression: {0}
  NullByte(#[from] NulError),
}

/// Failure to compress a stream into a buffer.
#[derive(Debug, Display, Error)]
pub enum CompressionError {
  /// other error: {0}
  Other(#[from] VectorscanRuntimeError),
  /// not enough space for {0} in buf {1:?}
  NoSpace(usize, Vec<u8>),
}

/// Wrapper for errors returned by
/// [`Scratch::scan_channel()`](crate::state::Scratch::scan_channel) and other
/// async scanning methods.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug, Display, Error)]
pub enum ScanError {
  /// error from return value of `hs_scan*()`: {0}
  ReturnValue(#[from] VectorscanRuntimeError),
  /// task join error: {0}
  Join(#[from] tokio::task::JoinError),
}

/// Top-level wrapper for errors returned by this library.
#[derive(Debug, Display, Error)]
#[ignore_extra_doc_attributes]
pub enum VectorscanError {
  /// error from the vectorscan runtime: {0}
  Runtime(#[from] VectorscanRuntimeError),
  /// compile error: {0}
  #[cfg(feature = "compiler")]
  #[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
  Compile(#[from] VectorscanCompileError),
  /// error during scan: {0}
  #[cfg(feature = "async")]
  #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
  Scan(#[from] ScanError),
  /// error compressing stream: {0}
  Compression(#[from] CompressionError),
}

/// Errors returned by methods in the chimera library.
#[cfg(feature = "chimera")]
#[cfg_attr(docsrs, doc(cfg(feature = "chimera")))]
pub mod chimera {
  use crate::{hs, matchers::ExpressionIndex};

  use displaydoc::Display;
  use thiserror::Error;

  use std::{
    ffi::{CStr, NulError},
    fmt,
    os::raw::c_uint,
  };

  /// Native error code from the underlying chimera library.
  #[derive(
    Debug,
    Display,
    Error,
    Copy,
    Clone,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    num_enum::IntoPrimitive,
    num_enum::FromPrimitive,
  )]
  #[repr(i8)]
  #[ignore_extra_doc_attributes]
  pub enum ChimeraRuntimeError {
    /// A parameter passed to this function was invalid.
    Invalid = hs::CH_INVALID,
    /// A memory allocation failed.
    NoMem = hs::CH_NOMEM,
    /// The engine was terminated by callback.
    ///
    /// This return value indicates that the target buffer was partially
    /// scanned, but that the callback function returned
    /// [`ChimeraMatchResult::Terminate`](crate::matchers::chimera::ChimeraMatchResult::Terminate)
    /// after a match was located.
    ScanTerminated = hs::CH_SCAN_TERMINATED,
    /// The pattern compiler failed, and the [`ChimeraInnerCompileError`] should
    /// be inspected for more detail.
    CompilerError = hs::CH_COMPILER_ERROR,
    /// The given database was built for a different version of the Chimera
    /// matcher.
    DbVersionError = hs::CH_DB_VERSION_ERROR,
    /// The given database was built for a different platform (i.e., CPU type).
    DbPlatformError = hs::CH_DB_PLATFORM_ERROR,
    /// The given database was built for a different mode of operation.
    ///
    /// This error is returned when streaming calls are used with a
    /// non-streaming database and vice versa.
    DbModeError = hs::CH_DB_MODE_ERROR,
    /// A parameter passed to this function was not correctly aligned.
    BadAlign = hs::CH_BAD_ALIGN,
    /// The memory allocator did not correctly return memory suitably aligned
    /// for the largest representable data type on this platform.
    BadAlloc = hs::CH_BAD_ALLOC,
    /// The scratch region was already in use.
    ///
    /// This error is returned when Chimera is able to detect that the scratch
    /// region given is already in use by another Chimera API call.
    ///
    /// A separate scratch region, allocated with
    /// [`ChimeraScratch::setup_for_db()`](crate::state::chimera::ChimeraScratch::setup_for_db)
    /// or [`ChimeraScratch::try_clone()`](crate::state::chimera::ChimeraScratch::try_clone), is
    /// required for every concurrent caller of the Chimera API.
    ///
    /// For example, this error might be returned when
    /// [`ChimeraScratch::scan_sync()`](crate::state::chimera::ChimeraScratch::scan_sync)
    /// has been called inside a callback delivered by a currently-executing
    /// [`ChimeraScratch::scan_sync()`](crate::state::chimera::ChimeraScratch::scan_sync)
    /// call using the same scratch region.
    ///
    /// Note: Not all concurrent uses of scratch regions may be detected. This
    /// error is intended as a best-effort debugging tool, not a guarantee.
    ///
    /// Note: safe Rust code should never see this error. See [`crate::state`]
    /// for ways to manage scratch spaces.
    ScratchInUse = hs::CH_SCRATCH_IN_USE,
    /// Unexpected internal error from Vectorscan.
    ///
    /// This error indicates that there was unexpected matching behaviors from
    /// Vectorscan. This could be related to invalid usage of scratch space or
    /// invalid memory operations by users.
    #[num_enum(default)]
    UnknownError = hs::CH_UNKNOWN_HS_ERROR,
    /// Returned when pcre_exec (called for some expressions internally from
    /// [`ChimeraScratch::scan_sync()`](crate::state::chimera::ChimeraScratch::scan_sync))
    /// failed due to a fatal error.
    FailInternal = hs::CH_FAIL_INTERNAL,
  }

  impl ChimeraRuntimeError {
    pub(crate) fn from_native(x: hs::ch_error_t) -> Result<(), Self> {
      static_assertions::const_assert_eq!(0, hs::CH_SUCCESS);
      if x == 0 {
        Ok(())
      } else {
        let s: Self = (x as i8).into();
        Err(s)
      }
    }

    #[cfg(feature = "compiler")]
    pub(crate) fn copy_from_native_compile_error(
      x: hs::ch_error_t,
      c: *mut hs::ch_compile_error,
    ) -> Result<(), ChimeraCompileError> {
      match Self::from_native(x) {
        Ok(()) => Ok(()),
        Err(Self::CompilerError) => {
          let e = ChimeraInnerCompileError::copy_from_native(unsafe { &mut *c }).unwrap();
          Err(ChimeraCompileError::Compile(e))
        },
        Err(e) => Err(e.into()),
      }
    }
  }

  /// Error details returned by the pattern compiler.
  ///
  /// This is returned by the compile calls
  /// ([`ChimeraDb::compile()`](crate::database::chimera::ChimeraDb::compile)
  /// and [`ChimeraDb::compile_multi()`](crate::database::chimera::ChimeraDb::compile_multi)) on
  /// failure. The caller may inspect the values returned in this type to
  /// determine the cause of failure.
  #[derive(Debug, Error)]
  #[cfg(feature = "compiler")]
  #[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
  pub struct ChimeraInnerCompileError {
    /// A human-readable error message describing the error.
    ///
    /// Common errors are the same as for the base vectorscan library's
    /// [`super::CompileError::message`], except that PCRE constructs are fully
    /// supported and will not cause errors.
    pub message: String,
    /// The zero-based number of the expression that caused the error (if this
    /// can be determined). This value's behavior is the same as for the base
    /// vectorscan library's [`super::CompileError::expression`].
    pub expression: Option<ExpressionIndex>,
  }

  #[cfg(feature = "compiler")]
  impl fmt::Display for ChimeraInnerCompileError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
      write!(
        f,
        "chimera compile error(@{:?}): {}",
        &self.expression, &self.message
      )
    }
  }

  #[cfg(feature = "compiler")]
  impl ChimeraInnerCompileError {
    pub(crate) fn copy_from_native(
      x: &mut hs::ch_compile_error,
    ) -> Result<Self, ChimeraRuntimeError> {
      let hs::ch_compile_error {
        message,
        expression,
      } = x;
      assert!(!message.is_null());
      let ret = Self {
        message: unsafe { CStr::from_ptr(*message) }
          .to_string_lossy()
          .to_string(),
        expression: if *expression < 0 {
          None
        } else {
          Some(ExpressionIndex(*expression as c_uint))
        },
      };
      ChimeraRuntimeError::from_native(unsafe { hs::ch_free_compile_error(x) })?;
      Ok(ret)
    }
  }

  /// Wrapper for errors returned when parsing or compiling expressions.
  #[cfg(feature = "compiler")]
  #[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
  #[derive(Debug, Display, Error)]
  pub enum ChimeraCompileError {
    /// non-compilation error: {0}
    NonCompile(#[from] ChimeraRuntimeError),
    /// pattern compilation error: {0}
    Compile(#[from] ChimeraInnerCompileError),
    /// null byte in expression: {0}
    NullByte(#[from] NulError),
  }

  /// Native error code for non-fatal match errors from PCRE execution.
  #[derive(
    Debug,
    Display,
    Error,
    Copy,
    Clone,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    num_enum::IntoPrimitive,
    num_enum::TryFromPrimitive,
  )]
  #[repr(u8)]
  pub enum ChimeraMatchErrorType {
    /// PCRE hits its match limit and reports `PCRE_ERROR_MATCHLIMIT`.
    MatchLimit = hs::CH_ERROR_MATCHLIMIT,
    /// PCRE hits its recursion limit and reports `PCRE_ERROR_RECURSIONLIMIT`.
    RecursionLimit = hs::CH_ERROR_RECURSIONLIMIT,
  }

  impl ChimeraMatchErrorType {
    pub(crate) fn from_native(x: hs::ch_error_event_t) -> Self { (x as u8).try_into().unwrap() }
  }

  /// Error type for non-fatal match errors from PCRE execution during
  /// [`ChimeraScratch::scan_sync()`](crate::state::chimera::ChimeraScratch::scan_sync).
  #[derive(Debug, Error)]
  pub struct ChimeraMatchError {
    /// The type of error that occurred.
    #[source]
    pub error_type: ChimeraMatchErrorType,
    /// The ID number of the expression that failed.
    pub id: ExpressionIndex,
  }

  impl fmt::Display for ChimeraMatchError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
      write!(f, "{}@{}", self.error_type, self.id)
    }
  }

  /// Wrapper for errors returned by
  /// [`ChimeraScratch::scan_channel()`](crate::state::chimera::ChimeraScratch::scan_channel).
  #[cfg(feature = "async")]
  #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
  #[derive(Debug, Display, Error)]
  pub enum ChimeraScanError {
    /// error from return value of `ch_scan()`: {0}
    ReturnValue(#[from] ChimeraRuntimeError),
    /// non-fatal match error: {0}
    MatchError(#[from] ChimeraMatchError),
    /// task join error: {0}
    Join(#[from] tokio::task::JoinError),
  }

  /// Top-level wrapper for errors returned by the chimera library.
  #[derive(Debug, Display, Error)]
  #[ignore_extra_doc_attributes]
  pub enum ChimeraError {
    /// error from chimera runtime: {0}
    Runtime(#[from] ChimeraRuntimeError),
    /// error from vectorscan runtime: {0}
    ///
    /// This case in particular is helpful to convert the result of
    /// [`Platform::local()`](crate::flags::platform::Platform::local) into a
    /// chimera error.
    #[cfg(feature = "compiler")]
    #[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
    VectorscanRuntime(#[from] super::VectorscanRuntimeError),
    /// compile error: {0}
    #[cfg(feature = "compiler")]
    #[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
    Compile(#[from] ChimeraCompileError),
    /// error during chimera scan: {0}
    #[cfg(feature = "async")]
    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
    Scan(#[from] ChimeraScanError),
  }
}