vectorscan/database.rs
1/* Copyright 2022-2024 Danny McClanahan */
2/* SPDX-License-Identifier: BSD-3-Clause */
3
4//! Compile state machines from expressions or deserialize them from bytes.
5//!
6//! Vectorscan supports two distinct types of databases:
7//! - [`Database`]: from the base vectorscan library and supports [`Expression`]
8//! and [`Literal`] patterns.
9//! - [`chimera::ChimeraDb`]: from the chimera library and supports
10//! [`ChimeraExpression`](crate::expression::chimera::ChimeraExpression)
11//! patterns.
12//!
13//! Each database type serves as the entry point to the pattern compiler
14//! methods, such as [`Database::compile()`] and
15//! [`chimera::ChimeraDb::compile()`].
16//!
17//! # Database Instantiation
18//! The base vectorscan library offers a serialization interface for database
19//! objects which allows them to be transferred across hosts. The
20//! [`SerializedDb`] type provides an interface to locate serialized data from
21//! multiple locations. Consumers of this crate which disable the `"compiler"`
22//! feature can still search against strings by deserializing a database from
23//! bytes.
24//!
25//! The chimera library does not support database serialization, so databases
26//! must be created by compilation.
27
28#[cfg(feature = "stream")]
29use crate::stream::LiveStream;
30#[cfg(feature = "compiler")]
31use crate::{
32 error::VectorscanCompileError,
33 expression::{Expression, ExpressionSet, Literal, LiteralSet},
34 flags::{platform::Platform, Flags, Mode},
35};
36use crate::{error::VectorscanRuntimeError, hs, state::Scratch};
37
38use std::{
39 cmp,
40 ffi::CStr,
41 fmt, hash,
42 mem::{self, MaybeUninit},
43 ops,
44 os::raw::c_char,
45 ptr, slice, str,
46};
47
48/// Pointer type for db allocations used in [`Database#Managing
49/// Allocations`](Database#managing-allocations).
50pub type NativeDb = hs::hs_database;
51
52/// Read-only description of an in-memory state machine.
53///
54/// This type also serves as the entry point to the various types of [pattern
55/// compilers](#pattern-compilers), including literals, sets, and literal sets.
56#[derive(Debug)]
57#[repr(transparent)]
58pub struct Database(*mut NativeDb);
59
60/// # Convenience Methods
61/// These methods prepare some resource within a new heap allocation and are
62/// useful for doctests and examples.
63///
64/// ## Scratch Setup
65/// Databases already require their own heap allocation, which can be managed
66/// with the methods in [Managing Allocations](#managing-allocations). However,
67/// databases also impose a sort of implicit dynamic lifetime constraint on
68/// [`Scratch`] objects, which must be initialized against a db with
69/// [`Scratch::setup_for_db()`] before vectorscan can do any searching.
70///
71/// It is encouraged to re-use [`Scratch`] objects across databases where
72/// possible to minimize unnecessary allocations, but
73/// [`Self::allocate_scratch()`] is provided as a convenience method to quickly
74/// produce a 1:1 db:scratch mapping.
75///
76/// ## Serialization
77/// While [`SerializedDb`] offers a rich interface to wrap serialized bytes from
78/// a variety of sources with [`alloc::DbAllocation`], [`Self::serialize()`]
79/// simply returns a newly allocated region of bytes.
80impl Database {
81 /// Call [`Scratch::setup_for_db()`] on a newly allocated [`Scratch::blank`].
82 ///
83 ///```
84 /// #[cfg(feature = "compiler")]
85 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
86 /// use vectorscan::{expression::*, flags::*, matchers::*};
87 ///
88 /// let expr: Expression = "a+".parse()?;
89 /// let db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
90 /// let mut scratch = db.allocate_scratch()?;
91 ///
92 /// let mut matches: Vec<&str> = Vec::new();
93 /// scratch
94 /// .scan_sync(&db, "aardvark".into(), |Match { source, .. }| {
95 /// matches.push(unsafe { source.as_str() });
96 /// MatchResult::Continue
97 /// })?;
98 /// assert_eq!(&matches, &["a", "aa", "a"]);
99 /// Ok(())
100 /// }
101 /// # #[cfg(not(feature = "compiler"))]
102 /// # fn main() {}
103 /// ```
104 pub fn allocate_scratch(&self) -> Result<Scratch, VectorscanRuntimeError> {
105 let mut scratch = Scratch::blank();
106 scratch.setup_for_db(self)?;
107 Ok(scratch)
108 }
109
110 /// Call [`LiveStream::open()`] on `self`.
111 ///
112 ///```
113 /// #[cfg(feature = "compiler")]
114 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
115 /// use vectorscan::{expression::*, flags::*, matchers::*, stream::*};
116 /// use std::ops::Range;
117 ///
118 /// let expr: Expression = "a+".parse()?;
119 /// let db = expr.compile(Flags::SOM_LEFTMOST, Mode::STREAM | Mode::SOM_HORIZON_SMALL)?;
120 /// let scratch = db.allocate_scratch()?;
121 /// let live = db.allocate_stream()?;
122 ///
123 /// let data = "aardvark";
124 /// let mut matches: Vec<&str> = Vec::new();
125 /// let mut match_fn = |StreamMatch { range, .. }| {
126 /// let range: Range<usize> = range.into();
127 /// matches.push(&data[range]);
128 /// MatchResult::Continue
129 /// };
130 /// {
131 /// let matcher = StreamMatcher::new(&mut match_fn);
132 /// let mut sink = ScratchStreamSink::new(live, matcher, scratch);
133 ///
134 /// sink.scan(data.into())?;
135 /// sink.flush_eod()?;
136 /// }
137 /// assert_eq!(&matches, &["a", "aa", "a"]);
138 /// Ok(())
139 /// }
140 /// # #[cfg(not(feature = "compiler"))]
141 /// # fn main() {}
142 /// ```
143 #[cfg(feature = "stream")]
144 #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
145 pub fn allocate_stream(&self) -> Result<LiveStream, VectorscanRuntimeError> {
146 LiveStream::open(self)
147 }
148
149 /// Allocate a new memory region and serialize this in-memory state machine
150 /// into it.
151 ///
152 ///```
153 /// #[cfg(feature = "compiler")]
154 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
155 /// use vectorscan::{expression::*, flags::*, matchers::*};
156 ///
157 /// // Create a db to match against:
158 /// let expr: Expression = "a+".parse()?;
159 /// let db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
160 ///
161 /// // Serialize and deserialize the db:
162 /// let db = db.serialize()?.deserialize_db()?;
163 /// let mut scratch = db.allocate_scratch()?;
164 ///
165 /// // Search against the db:
166 /// let mut matches: Vec<&str> = Vec::new();
167 /// scratch
168 /// .scan_sync(&db, "aardvark".into(), |Match { source, .. }| {
169 /// matches.push(unsafe { source.as_str() });
170 /// MatchResult::Continue
171 /// })?;
172 /// assert_eq!(&matches, &["a", "aa", "a"]);
173 /// Ok(())
174 /// }
175 /// # #[cfg(not(feature = "compiler"))]
176 /// # fn main() {}
177 /// ```
178 pub fn serialize(&self) -> Result<SerializedDb<'static>, VectorscanRuntimeError> {
179 SerializedDb::serialize_db(self)
180 }
181}
182
183/// # Pattern Compilers
184/// Vectorscan supports compiling state machines for PCRE-like and literal
185/// pattern strings, as well as parallel sets of those patterns (although note
186/// that literal and non-literal patterns cannot be mixed). Each compile method
187/// supports a subset of all [`Flags`] arguments, documented in each method.
188///
189/// ## Platform Compatibility
190/// Each method also accepts an optional [`Platform`] object,
191/// which is used to select processor features to compile the database for.
192/// While the default of [`None`] will enable all features available to the
193/// current processor, some features can be disabled in order to produce a
194/// database which can execute on a wider variety of target platforms
195/// after being deserialized from a remote source.
196///
197///```
198/// #[cfg(all(feature = "compiler", feature = "stream"))]
199/// fn main() -> Result<(), vectorscan::error::VectorscanError> {
200/// use vectorscan::{expression::*, flags::{*, platform::*}, database::*};
201/// use std::slice;
202///
203/// let expr: Expression = "a+".parse()?;
204///
205/// // Verify that the current platform has AVX2 instructions, and make a db:
206/// let plat = Platform::local()?;
207/// assert!(plat.cpu_features.contains(&CpuFeatures::AVX2));
208/// assert!(plat != Platform::GENERIC);
209/// let db_with_avx2 = Database::compile(
210/// &expr,
211/// Flags::default(),
212/// Mode::STREAM,
213/// Some(&plat),
214/// )?;
215///
216/// // The only specialized instructions we have available are AVX2:
217/// assert_eq!(CpuFeatures::NONE, plat.cpu_features & !CpuFeatures::AVX2);
218/// // Avoid using AVX2 instructions:
219/// let db_no_avx2 = Database::compile(
220/// &expr,
221/// Flags::default(),
222/// Mode::STREAM,
223/// Some(&Platform::GENERIC),
224/// )?;
225///
226/// // Instruction selection does not affect the size of the state machine:
227/// assert!(db_with_avx2.database_size()? == db_no_avx2.database_size()?);
228/// assert!(db_with_avx2.stream_size()? == db_no_avx2.stream_size()?);
229///
230/// // Now create a db with None for the platform:
231/// let db_local = Database::compile(&expr, Flags::default(), Mode::STREAM, None)?;
232/// assert!(db_with_avx2.database_size()? == db_local.database_size()?);
233/// let n = db_with_avx2.database_size()?;
234///
235/// // Using None produces the same db as Platform::local():
236/// assert!(db_with_avx2.info()? == db_local.info()?);
237/// assert!(db_no_avx2.info()? != db_local.info()?);
238///
239/// // The "same" db does *not* apply to the in-memory representation:
240/// let db_data_1 = unsafe { slice::from_raw_parts(
241/// db_with_avx2.as_ref_native() as *const NativeDb as *const u8,
242/// n,
243/// )};
244/// let db_data_2 = unsafe { slice::from_raw_parts(
245/// db_no_avx2.as_ref_native() as *const NativeDb as *const u8,
246/// n,
247/// )};
248/// let db_data_3 = unsafe { slice::from_raw_parts(
249/// db_local.as_ref_native() as *const NativeDb as *const u8,
250/// n,
251/// )};
252/// assert!(db_data_1 != db_data_3);
253/// assert!(db_data_1 != db_data_2);
254/// Ok(())
255/// }
256/// # #[cfg(not(all(feature = "compiler", feature = "stream")))]
257/// # fn main() {}
258/// ```
259///
260/// ## Dynamic Memory Allocation
261/// These methods allocate a new region of memory using the db allocator (which
262/// can be overridden with [`crate::alloc::set_db_allocator()`]). That
263/// allocation can be manipulated as described in [Managing
264/// Allocations](#managing-allocations).
265#[cfg(feature = "compiler")]
266#[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
267impl Database {
268 /// Single pattern compiler.
269 ///
270 /// # Accepted Flags
271 /// - [`CASELESS`](Flags::CASELESS)
272 /// - [`DOTALL`](Flags::DOTALL)
273 /// - [`MULTILINE`](Flags::MULTILINE)
274 /// - [`SINGLEMATCH`](Flags::SINGLEMATCH)
275 /// - [`ALLOWEMPTY`](Flags::ALLOWEMPTY)
276 /// - [`UTF8`](Flags::UTF8)
277 /// - [`UCP`](Flags::UCP)
278 /// - [`PREFILTER`](Flags::PREFILTER)
279 /// - [`SOM_LEFTMOST`](Flags::SOM_LEFTMOST)
280 /// - [`COMBINATION`](Flags::COMBINATION)
281 /// - [`QUIET`](Flags::QUIET)
282 ///
283 ///```
284 /// # fn main() -> Result<(), vectorscan::error::VectorscanError> {
285 /// use vectorscan::{expression::*, flags::*, database::*, matchers::*};
286 ///
287 /// let expr: Expression = "hell(o)?".parse()?;
288 /// let db = Database::compile(&expr, Flags::default(), Mode::BLOCK, None)?;
289 ///
290 /// let mut scratch = db.allocate_scratch()?;
291 ///
292 /// let mut matches: Vec<&str> = Vec::new();
293 /// scratch
294 /// .scan_sync(&db, "hello".into(), |m| {
295 /// matches.push(unsafe { m.source.as_str() });
296 /// MatchResult::Continue
297 /// })?;
298 /// assert_eq!(&matches, &["hell", "hello"]);
299 /// # Ok(())
300 /// # }
301 /// ```
302 pub fn compile(
303 expression: &Expression,
304 flags: Flags,
305 mode: Mode,
306 platform: Option<&Platform>,
307 ) -> Result<Self, VectorscanCompileError> {
308 let mut db = ptr::null_mut();
309 let mut compile_err = ptr::null_mut();
310 let platform: Option<hs::hs_platform_info> = platform.cloned().map(Platform::into_native);
311 VectorscanRuntimeError::copy_from_native_compile_error(
312 unsafe {
313 hs::hs_compile(
314 expression.as_ptr(),
315 flags.into_native(),
316 mode.into_native(),
317 platform
318 .as_ref()
319 .map(|p| p as *const hs::hs_platform_info)
320 .unwrap_or(ptr::null()),
321 &mut db,
322 &mut compile_err,
323 )
324 },
325 compile_err,
326 )?;
327 Ok(unsafe { Self::from_native(db) })
328 }
329
330 /// Multiple pattern compiler.
331 ///
332 /// # Accepted Flags
333 /// - [`CASELESS`](Flags::CASELESS)
334 /// - [`DOTALL`](Flags::DOTALL)
335 /// - [`MULTILINE`](Flags::MULTILINE)
336 /// - [`SINGLEMATCH`](Flags::SINGLEMATCH)
337 /// - [`ALLOWEMPTY`](Flags::ALLOWEMPTY)
338 /// - [`UTF8`](Flags::UTF8)
339 /// - [`UCP`](Flags::UCP)
340 /// - [`PREFILTER`](Flags::PREFILTER)
341 /// - [`SOM_LEFTMOST`](Flags::SOM_LEFTMOST)
342 /// - [`COMBINATION`](Flags::COMBINATION)
343 /// - [`QUIET`](Flags::QUIET)
344 ///
345 ///```
346 /// # fn main() -> Result<(), vectorscan::error::VectorscanError> {
347 /// use vectorscan::{expression::*, flags::*, database::*, matchers::*};
348 ///
349 /// let a_expr: Expression = "a+".parse()?;
350 /// let b_expr: Expression = "b+".parse()?;
351 ///
352 /// // Example of providing ExprExt info (not available in ::compile()!):
353 /// let ext = ExprExt::from_min_length(1);
354 ///
355 /// let expr_set = ExpressionSet::from_exprs([&a_expr, &b_expr])
356 /// .with_ids([ExprId(1), ExprId(2)])
357 /// .with_exts([None, Some(&ext)]);
358 ///
359 /// let db = Database::compile_multi(&expr_set, Mode::BLOCK, None)?;
360 ///
361 /// let mut scratch = db.allocate_scratch()?;
362 ///
363 /// let mut matches: Vec<&str> = Vec::new();
364 /// scratch
365 /// .scan_sync(&db, "aardvark".into(), |m| {
366 /// matches.push(unsafe { m.source.as_str() });
367 /// MatchResult::Continue
368 /// })?;
369 /// assert_eq!(&matches, &["a", "aa", "aardva"]);
370 ///
371 /// matches.clear();
372 /// scratch
373 /// .scan_sync(&db, "imbibe".into(), |m| {
374 /// matches.push(unsafe { m.source.as_str() });
375 /// MatchResult::Continue
376 /// })?;
377 /// assert_eq!(&matches, &["imb", "imbib"]);
378 /// # Ok(())
379 /// # }
380 /// ```
381 pub fn compile_multi(
382 expression_set: &ExpressionSet,
383 mode: Mode,
384 platform: Option<&Platform>,
385 ) -> Result<Self, VectorscanCompileError> {
386 let mut db = ptr::null_mut();
387 let mut compile_err = ptr::null_mut();
388 let platform: Option<hs::hs_platform_info> = platform.cloned().map(Platform::into_native);
389 VectorscanRuntimeError::copy_from_native_compile_error(
390 unsafe {
391 if let Some(exts_ptr) = expression_set.exts_ptr() {
392 hs::hs_compile_ext_multi(
393 expression_set.expressions_ptr(),
394 expression_set.flags_ptr(),
395 expression_set.ids_ptr(),
396 exts_ptr,
397 expression_set.num_elements(),
398 mode.into_native(),
399 platform
400 .as_ref()
401 .map(|p| p as *const hs::hs_platform_info)
402 .unwrap_or(ptr::null()),
403 &mut db,
404 &mut compile_err,
405 )
406 } else {
407 hs::hs_compile_multi(
408 expression_set.expressions_ptr(),
409 expression_set.flags_ptr(),
410 expression_set.ids_ptr(),
411 expression_set.num_elements(),
412 mode.into_native(),
413 platform
414 .as_ref()
415 .map(|p| p as *const hs::hs_platform_info)
416 .unwrap_or(ptr::null()),
417 &mut db,
418 &mut compile_err,
419 )
420 }
421 },
422 compile_err,
423 )?;
424 Ok(unsafe { Self::from_native(db) })
425 }
426
427 /// Single literal compiler.
428 ///
429 /// # Accepted Flags
430 /// - [`CASELESS`](Flags::CASELESS)
431 /// - [`SINGLEMATCH`](Flags::SINGLEMATCH)
432 /// - [`SOM_LEFTMOST`](Flags::SOM_LEFTMOST)
433 ///
434 ///```
435 /// # fn main() -> Result<(), vectorscan::error::VectorscanError> {
436 /// use vectorscan::{expression::*, flags::*, database::*, matchers::*};
437 ///
438 /// let expr: Literal = "he\0ll".parse()?;
439 /// let db = Database::compile_literal(&expr, Flags::default(), Mode::BLOCK, None)?;
440 ///
441 /// let mut scratch = db.allocate_scratch()?;
442 ///
443 /// let mut matches: Vec<&str> = Vec::new();
444 /// scratch
445 /// .scan_sync(&db, "he\0llo".into(), |m| {
446 /// matches.push(unsafe { m.source.as_str() });
447 /// MatchResult::Continue
448 /// })?;
449 /// assert_eq!(&matches, &["he\0ll"]);
450 /// # Ok(())
451 /// # }
452 /// ```
453 pub fn compile_literal(
454 literal: &Literal,
455 flags: Flags,
456 mode: Mode,
457 platform: Option<&Platform>,
458 ) -> Result<Self, VectorscanCompileError> {
459 let mut db = ptr::null_mut();
460 let mut compile_err = ptr::null_mut();
461 let platform: Option<hs::hs_platform_info> = platform.cloned().map(Platform::into_native);
462 VectorscanRuntimeError::copy_from_native_compile_error(
463 unsafe {
464 hs::hs_compile_lit(
465 literal.as_ptr(),
466 flags.into_native(),
467 literal.as_bytes().len(),
468 mode.into_native(),
469 platform
470 .as_ref()
471 .map(|p| p as *const hs::hs_platform_info)
472 .unwrap_or(ptr::null()),
473 &mut db,
474 &mut compile_err,
475 )
476 },
477 compile_err,
478 )?;
479 Ok(unsafe { Self::from_native(db) })
480 }
481
482 /// Multiple literal compiler.
483 ///
484 /// # Accepted Flags
485 /// - [`CASELESS`](Flags::CASELESS)
486 /// - [`SINGLEMATCH`](Flags::SINGLEMATCH)
487 /// - [`SOM_LEFTMOST`](Flags::SOM_LEFTMOST)
488 ///
489 ///```
490 /// # fn main() -> Result<(), vectorscan::error::VectorscanError> {
491 /// use vectorscan::{expression::*, flags::*, database::*, matchers::*};
492 ///
493 /// let hell_lit: Literal = "he\0ll".parse()?;
494 /// let free_lit: Literal = "fr\0e\0e".parse()?;
495 /// let lit_set = LiteralSet::from_lits([&hell_lit, &free_lit])
496 /// .with_flags([Flags::default(), Flags::default()])
497 /// .with_ids([ExprId(2), ExprId(1)]);
498 ///
499 /// let db = Database::compile_multi_literal(&lit_set, Mode::BLOCK, None)?;
500 ///
501 /// let mut scratch = db.allocate_scratch()?;
502 ///
503 /// let mut matches: Vec<(u32, &str)> = Vec::new();
504 /// scratch
505 /// .scan_sync(
506 /// &db,
507 /// "he\0llo".into(),
508 /// |Match { id: ExpressionIndex(id), source, .. }| {
509 /// matches.push((id, unsafe { source.as_str() }));
510 /// MatchResult::Continue
511 /// })?;
512 /// assert_eq!(&matches, &[(2, "he\0ll")]);
513 ///
514 /// matches.clear();
515 /// scratch
516 /// .scan_sync(
517 /// &db,
518 /// "fr\0e\0edom".into(),
519 /// |Match { id: ExpressionIndex(id), source, .. }| {
520 /// matches.push((id, unsafe { source.as_str() }));
521 /// MatchResult::Continue
522 /// })?;
523 /// assert_eq!(&matches, &[(1, "fr\0e\0e")]);
524 /// # Ok(())
525 /// # }
526 /// ```
527 pub fn compile_multi_literal(
528 literal_set: &LiteralSet,
529 mode: Mode,
530 platform: Option<&Platform>,
531 ) -> Result<Self, VectorscanCompileError> {
532 let mut db = ptr::null_mut();
533 let mut compile_err = ptr::null_mut();
534 let platform: Option<hs::hs_platform_info> = platform.cloned().map(Platform::into_native);
535 VectorscanRuntimeError::copy_from_native_compile_error(
536 unsafe {
537 hs::hs_compile_lit_multi(
538 literal_set.literals_ptr(),
539 literal_set.flags_ptr(),
540 literal_set.ids_ptr(),
541 literal_set.lengths_ptr(),
542 literal_set.num_elements(),
543 mode.into_native(),
544 platform
545 .as_ref()
546 .map(|p| p as *const hs::hs_platform_info)
547 .unwrap_or(ptr::null()),
548 &mut db,
549 &mut compile_err,
550 )
551 },
552 compile_err,
553 )?;
554 Ok(unsafe { Self::from_native(db) })
555 }
556}
557
558/// # Introspection
559/// These methods extract various bits of runtime information from the db.
560impl Database {
561 /// Return the size of the db allocation.
562 ///
563 /// Using [`Flags::UCP`] explodes the size of character classes, which
564 /// increases the size of the state machine:
565 ///
566 ///```
567 /// #[cfg(feature = "compiler")]
568 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
569 /// use vectorscan::{expression::*, flags::*};
570 ///
571 /// let expr: Expression = r"\w".parse()?;
572 /// let utf8_db = expr.compile(Flags::UTF8 | Flags::UCP, Mode::BLOCK)?;
573 /// let ascii_db = expr.compile(Flags::default(), Mode::BLOCK)?;
574 ///
575 /// // Including UTF-8 classes increases the size:
576 /// assert!(utf8_db.database_size()? > ascii_db.database_size()?);
577 /// Ok(())
578 /// }
579 /// # #[cfg(not(feature = "compiler"))]
580 /// # fn main() {}
581 /// ```
582 ///
583 /// This size corresponds to the requested allocation size passed to the db
584 /// allocator:
585 ///
586 ///```
587 /// #[cfg(all(feature = "alloc", feature = "compiler"))]
588 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
589 /// use vectorscan::{expression::*, flags::*, alloc::*};
590 /// use std::alloc::System;
591 ///
592 /// // Wrap the standard Rust System allocator.
593 /// let tracker = LayoutTracker::new(System.into());
594 /// // Register it as the allocator for databases.
595 /// assert!(set_db_allocator(tracker)?.is_none());
596 ///
597 /// let expr: Expression = r"\w".parse()?;
598 /// let utf8_db = expr.compile(Flags::UTF8 | Flags::UCP, Mode::BLOCK)?;
599 ///
600 /// // Get the database allocator we just registered and view its live allocations:
601 /// let allocs = get_db_allocator().as_ref().unwrap().current_allocations();
602 /// // Verify that only the single known db was allocated:
603 /// assert_eq!(1, allocs.len());
604 /// let (_p, layout) = allocs[0];
605 ///
606 /// // Verify that the allocation size is the same as reported:
607 /// assert_eq!(layout.size(), utf8_db.database_size()?);
608 /// Ok(())
609 /// }
610 /// # #[cfg(not(all(feature = "alloc", feature = "compiler")))]
611 /// # fn main() {}
612 /// ```
613 pub fn database_size(&self) -> Result<usize, VectorscanRuntimeError> {
614 let mut ret: MaybeUninit<usize> = MaybeUninit::uninit();
615 VectorscanRuntimeError::from_native(unsafe {
616 hs::hs_database_size(self.as_ref_native(), ret.as_mut_ptr())
617 })?;
618 Ok(unsafe { ret.assume_init() })
619 }
620
621 /// Return the amount of space necessary to maintain stream state for this db.
622 ///
623 ///```
624 /// #[cfg(feature = "compiler")]
625 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
626 /// use vectorscan::{expression::*, flags::*};
627 ///
628 /// let expr: Expression = r"\w".parse()?;
629 /// let utf8_db = expr.compile(Flags::UTF8 | Flags::UCP, Mode::STREAM)?;
630 /// let ascii_db = expr.compile(Flags::default(), Mode::STREAM)?;
631 ///
632 /// // Including UTF-8 classes increases both db and stream size:
633 /// assert!(utf8_db.database_size()? > ascii_db.database_size()?);
634 /// assert!(utf8_db.stream_size()? > ascii_db.stream_size()?);
635 /// Ok(())
636 /// }
637 /// # #[cfg(not(feature = "compiler"))]
638 /// # fn main() {}
639 /// ```
640 ///
641 /// This size corresponds to the requested allocation size passed to the
642 /// stream allocator:
643 ///
644 ///```
645 /// #[cfg(all(feature = "alloc", feature = "compiler"))]
646 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
647 /// use vectorscan::{expression::*, flags::*, alloc::*, stream::*};
648 /// use std::alloc::System;
649 ///
650 /// // Wrap the standard Rust System allocator.
651 /// let tracker = LayoutTracker::new(System.into());
652 /// // Register it as the allocator for streams.
653 /// assert!(set_stream_allocator(tracker)?.is_none());
654 ///
655 /// let expr: Expression = r"\w".parse()?;
656 /// let db = expr.compile(Flags::UTF8 | Flags::UCP, Mode::STREAM)?;
657 /// let _stream = LiveStream::open(&db)?;
658 ///
659 /// // Get the stream allocator we just registered and view its live allocations:
660 /// let allocs = get_stream_allocator().as_ref().unwrap().current_allocations();
661 /// // Verify that only the single known stream was allocated:
662 /// assert_eq!(1, allocs.len());
663 /// let (_p, layout) = allocs[0];
664 ///
665 /// // Verify that the allocation size is the same as reported:
666 /// assert_eq!(layout.size(), db.stream_size()?);
667 /// Ok(())
668 /// }
669 /// # #[cfg(not(all(feature = "alloc", feature = "compiler")))]
670 /// # fn main() {}
671 /// ```
672 #[cfg(feature = "stream")]
673 #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
674 pub fn stream_size(&self) -> Result<usize, VectorscanRuntimeError> {
675 let mut ret: MaybeUninit<usize> = MaybeUninit::uninit();
676 VectorscanRuntimeError::from_native(unsafe {
677 hs::hs_stream_size(self.as_ref_native(), ret.as_mut_ptr())
678 })?;
679 Ok(unsafe { ret.assume_init() })
680 }
681
682 /// Extract metadata about the current database into a new string allocation.
683 ///
684 /// This is a convenience method that simply calls
685 /// [`DbInfo::extract_db_info()`].
686 ///
687 ///```
688 /// #[cfg(feature = "compiler")]
689 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
690 /// use vectorscan::{expression::*, flags::*};
691 ///
692 /// let expr: Expression = "a+".parse()?;
693 /// let db = expr.compile(Flags::default(), Mode::BLOCK)?;
694 /// let info = db.info()?;
695 /// assert_eq!(info.as_str(), "Version: 5.4.11 Features: AVX2 Mode: BLOCK");
696 /// Ok(())
697 /// }
698 /// # #[cfg(not(feature = "compiler"))]
699 /// # fn main() {}
700 /// ```
701 pub fn info(&self) -> Result<DbInfo, VectorscanRuntimeError> { DbInfo::extract_db_info(self) }
702}
703
704/// # Managing Allocations
705/// These methods provide access to the underlying memory allocation containing
706/// the data for the in-memory state machine. They can be used along with
707/// [`SerializedDb::deserialize_db_at()`] to control the memory location used
708/// for the state machine, or to preserve db allocations across weird lifetime
709/// constraints.
710///
711/// Note that [`Self::database_size()`] can be used to determine the size of the
712/// memory allocation pointed to by [`Self::as_ref_native()`] and
713/// [`Self::as_mut_native()`].
714impl Database {
715 /// Wrap the provided allocation `p`.
716 ///
717 /// # Safety
718 /// The pointer `p` must point to an initialized db allocation prepared by one
719 /// of the compile or deserialize methods.
720 ///
721 /// This method also makes it especially easy to create multiple references to
722 /// the same allocation, which will then cause a double free when
723 /// [`Self::try_drop()`] is called more than once for the same db allocation.
724 /// To avoid this, wrap the result in a [`ManuallyDrop`](mem::ManuallyDrop):
725 ///
726 ///```
727 /// #[cfg(feature = "compiler")]
728 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
729 /// use vectorscan::{expression::*, flags::*, matchers::*, database::*, state::*};
730 /// use std::mem::ManuallyDrop;
731 ///
732 /// // Compile a legitimate db:
733 /// let expr: Expression = "a+".parse()?;
734 /// let mut db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
735 ///
736 /// // Create two new references to that allocation,
737 /// // wrapped to avoid calling the drop code:
738 /// let db_ptr: *mut NativeDb = db.as_mut_native();
739 /// let db_ref_1 = ManuallyDrop::new(unsafe { Database::from_native(db_ptr) });
740 /// let db_ref_2 = ManuallyDrop::new(unsafe { Database::from_native(db_ptr) });
741 ///
742 /// // Both db references are valid and can be used for matching.
743 /// let mut scratch = Scratch::blank();
744 /// scratch.setup_for_db(&db_ref_1)?;
745 /// scratch.setup_for_db(&db_ref_2)?;
746 ///
747 /// let mut matches: Vec<&str> = Vec::new();
748 /// scratch
749 /// .scan_sync(&db_ref_1, "aardvark".into(), |Match { source, .. }| {
750 /// matches.push(unsafe { source.as_str() });
751 /// MatchResult::Continue
752 /// })?;
753 /// scratch
754 /// .scan_sync(&db_ref_2, "aardvark".into(), |Match { source, .. }| {
755 /// matches.push(unsafe { source.as_str() });
756 /// MatchResult::Continue
757 /// })?;
758 /// assert_eq!(&matches, &["a", "aa", "a", "a", "aa", "a"]);
759 /// Ok(())
760 /// }
761 /// # #[cfg(not(feature = "compiler"))]
762 /// # fn main() {}
763 /// ```
764 pub const unsafe fn from_native(p: *mut NativeDb) -> Self { Self(p) }
765
766 /// Get a read-only reference to the db allocation.
767 ///
768 /// This method is mostly used internally and cast to a pointer to provide to
769 /// the vectorscan native library methods.
770 pub fn as_ref_native(&self) -> &NativeDb { unsafe { &*self.0 } }
771
772 /// Get a mutable reference to the db allocation.
773 ///
774 /// The result of this method can be cast to a pointer and provided to
775 /// [`Self::from_native()`].
776 pub fn as_mut_native(&mut self) -> &mut NativeDb { unsafe { &mut *self.0 } }
777
778 /// Free the underlying db allocation.
779 ///
780 /// # Safety
781 /// This method must be called at most once over the lifetime of each db
782 /// allocation. It is called by default on drop, so
783 /// [`ManuallyDrop`](mem::ManuallyDrop) is recommended to wrap instances
784 /// that reference external data in order to avoid attempting to free the
785 /// referenced data.
786 ///
787 /// ## Only Frees Memory
788 /// This method performs no processing other than freeing the allocated
789 /// memory, so it can be skipped without leaking resources if the
790 /// underlying [`NativeDb`] allocation is freed by some other means.
791 pub unsafe fn try_drop(&mut self) -> Result<(), VectorscanRuntimeError> {
792 VectorscanRuntimeError::from_native(unsafe { hs::hs_free_database(self.as_mut_native()) })
793 }
794}
795
796impl ops::Drop for Database {
797 fn drop(&mut self) {
798 unsafe {
799 self.try_drop().unwrap();
800 }
801 }
802}
803
804unsafe impl Send for Database {}
805
806/// Wrappers over allocations from various sources.
807///
808/// In particular, this module contains [`DbAllocation`](alloc::DbAllocation),
809/// which provides the logic needed to abstract over different sources of
810/// backing data used to contain a [`SerializedDb`].
811pub mod alloc {
812 use std::{borrow::Cow, ops, slice};
813
814 /// An allocation of memory using the misc allocator.
815 ///
816 /// The allocator used for this memory can be modified or accessed with
817 /// [`crate::alloc::set_misc_allocator()`] and
818 /// [`crate::alloc::get_misc_allocator()`] if the `"alloc"` feature is
819 /// enabled.
820 ///
821 /// The backing memory will be deallocated by the misc allocator upon drop
822 /// unless this is wrapped with a [`ManuallyDrop`](std::mem::ManuallyDrop).
823 #[derive(Debug)]
824 pub struct MiscAllocation {
825 pub(crate) data: *mut u8,
826 pub(crate) len: usize,
827 }
828
829 unsafe impl Send for MiscAllocation {}
830
831 impl MiscAllocation {
832 pub(crate) const fn as_ptr(&self) -> *mut u8 { self.data }
833
834 pub(crate) const fn len(&self) -> usize { self.len }
835
836 /// Return a view over the backing memory region.
837 pub const fn as_slice(&self) -> &[u8] {
838 unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
839 }
840
841 unsafe fn free(&mut self) { crate::free_misc(self.data) }
842 }
843
844 impl ops::Drop for MiscAllocation {
845 fn drop(&mut self) {
846 unsafe {
847 self.free();
848 }
849 }
850 }
851
852 /// Wrapper over a misc or rust-level allocation.
853 ///
854 /// Used to provide [`super::SerializedDb`] with the ability to source data
855 /// allocated by the vectorscan library itself or by other Rust code.
856 #[derive(Debug)]
857 pub enum DbAllocation<'a> {
858 /// Memory was allocated with a `'static` lifetime using the registered misc
859 /// allocator.
860 Misc(MiscAllocation),
861 /// Memory was allocated with a known lifetime and may be owned or
862 /// referenced.
863 Rust(Cow<'a, [u8]>),
864 }
865
866 /// Methods available to all types of allocations.
867 impl<'a> DbAllocation<'a> {
868 pub(crate) fn as_ptr(&self) -> *const u8 {
869 match self {
870 Self::Misc(misc) => misc.as_ptr(),
871 Self::Rust(cow) => cow.as_ptr(),
872 }
873 }
874
875 pub(crate) fn len(&self) -> usize {
876 match self {
877 Self::Misc(misc) => misc.len(),
878 Self::Rust(cow) => cow.len(),
879 }
880 }
881
882 /// Return a view over the backing memory region, wherever it may come from.
883 pub fn as_slice(&self) -> &[u8] { unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) } }
884 }
885
886 /// Methods that produce new owned (`'static`) allocations.
887 ///
888 /// A [`Clone`] impl is also available for such owned allocations.
889 impl DbAllocation<'static> {
890 /// Copy the referenced data into a new Rust-level`'static` allocation.
891 pub fn from_cloned_data(s: &DbAllocation) -> Self {
892 let newly_allocated: Vec<u8> = s.as_slice().to_vec();
893 Self::Rust(Cow::Owned(newly_allocated))
894 }
895 }
896
897 impl Clone for DbAllocation<'static> {
898 fn clone(&self) -> Self { Self::from_cloned_data(self) }
899 }
900
901 /// Wrappers over allocations performed by the chimera library.
902 ///
903 /// Since chimera does not support database deserialization like the base
904 /// vectorscan library, there is no analogy to [`DbAllocation`].
905 #[cfg(feature = "chimera")]
906 #[cfg_attr(docsrs, doc(cfg(feature = "chimera")))]
907 pub mod chimera {
908 use std::{ops, slice};
909
910 /// An allocation of memory using the chimera misc allocator.
911 ///
912 /// The allocator used for this memory can be modified or accessed with
913 /// [`crate::alloc::chimera::set_chimera_misc_allocator()`] and
914 /// [`crate::alloc::chimera::get_chimera_misc_allocator()`] if the `"alloc"`
915 /// feature is enabled.
916 ///
917 /// The backing memory will be deallocated by the chimera misc allocator
918 /// upon drop unless this is wrapped with a
919 /// [`ManuallyDrop`](std::mem::ManuallyDrop).
920 #[derive(Debug)]
921 pub struct ChimeraMiscAllocation {
922 pub(crate) data: *mut u8,
923 pub(crate) len: usize,
924 }
925
926 unsafe impl Send for ChimeraMiscAllocation {}
927
928 impl ChimeraMiscAllocation {
929 pub(crate) const fn as_ptr(&self) -> *mut u8 { self.data }
930
931 pub(crate) const fn len(&self) -> usize { self.len }
932
933 /// Return a view over the backing memory region.
934 pub const fn as_slice(&self) -> &[u8] {
935 unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
936 }
937
938 unsafe fn free(&mut self) { crate::free_misc_chimera(self.data) }
939 }
940
941 impl ops::Drop for ChimeraMiscAllocation {
942 fn drop(&mut self) {
943 unsafe {
944 self.free();
945 }
946 }
947 }
948 }
949}
950
951/// Wrapper for allocated string data returned by [`Database::info()`].
952#[repr(transparent)]
953pub struct DbInfo(pub alloc::MiscAllocation);
954
955impl DbInfo {
956 const fn without_null(&self) -> impl slice::SliceIndex<[u8], Output=[u8]> { ..(self.0.len() - 1) }
957
958 /// Return a view of the allocated string data.
959 ///
960 /// Vectorscan will always return valid UTF-8 data for this string, so it
961 /// skips the validity check. Note that the returned string does not include
962 /// the trailing null byte allocated by the underlying vectorscan library.
963 pub fn as_str(&self) -> &str {
964 unsafe { str::from_utf8_unchecked(&self.0.as_slice()[self.without_null()]) }
965 }
966
967 /// Write out metadata for `db` into a newly allocated region.
968 pub fn extract_db_info(db: &Database) -> Result<Self, VectorscanRuntimeError> {
969 let mut info = ptr::null_mut();
970 VectorscanRuntimeError::from_native(unsafe {
971 hs::hs_database_info(db.as_ref_native(), &mut info)
972 })?;
973 let len = unsafe { CStr::from_ptr(info) }.to_bytes_with_nul().len();
974 assert!(len > 0);
975
976 let ret = alloc::MiscAllocation {
977 data: unsafe { mem::transmute(info) },
978 len,
979 };
980
981 Ok(Self(ret))
982 }
983}
984
985impl fmt::Debug for DbInfo {
986 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DbInfo({:?})", self.as_str()) }
987}
988
989impl fmt::Display for DbInfo {
990 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_str()) }
991}
992
993impl cmp::PartialEq for DbInfo {
994 fn eq(&self, other: &Self) -> bool { self.as_str().eq(other.as_str()) }
995}
996
997impl cmp::Eq for DbInfo {}
998
999impl cmp::PartialOrd for DbInfo {
1000 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) }
1001}
1002
1003impl cmp::Ord for DbInfo {
1004 fn cmp(&self, other: &Self) -> cmp::Ordering { self.as_str().cmp(other.as_str()) }
1005}
1006
1007impl hash::Hash for DbInfo {
1008 fn hash<H>(&self, state: &mut H)
1009 where H: hash::Hasher {
1010 self.as_str().hash(state);
1011 }
1012}
1013
1014/// Wrapper for a serialized form of a [`Database`].
1015#[derive(Debug)]
1016#[repr(transparent)]
1017pub struct SerializedDb<'a>(
1018 /// This serialization data can be sourced from a variety of places.
1019 pub alloc::DbAllocation<'a>,
1020);
1021
1022/// Methods available to all types of allocations.
1023impl<'a> SerializedDb<'a> {
1024 fn as_ptr(&self) -> *const c_char { unsafe { mem::transmute(self.0.as_ptr()) } }
1025
1026 fn len(&self) -> usize { self.0.len() }
1027
1028 /// Deserialize into a new db allocation.
1029 ///
1030 /// This will make a new allocation through the allocator from
1031 /// [`crate::alloc::set_db_allocator()`].
1032 ///
1033 ///```
1034 /// #[cfg(feature = "compiler")]
1035 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
1036 /// use vectorscan::{expression::*, flags::*};
1037 ///
1038 /// let expr: Expression = "a+".parse()?;
1039 /// let serialized_db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?.serialize()?;
1040 /// let db = serialized_db.deserialize_db()?;
1041 ///
1042 /// // Note that the expected deserialized size is the same
1043 /// // as the resulting in-memory database size:
1044 /// assert_eq!(db.database_size()?, serialized_db.deserialized_size()?);
1045 /// Ok(())
1046 /// }
1047 /// # #[cfg(not(feature = "compiler"))]
1048 /// # fn main() {}
1049 /// ```
1050 pub fn deserialize_db(&self) -> Result<Database, VectorscanRuntimeError> {
1051 let mut deserialized: MaybeUninit<*mut hs::hs_database> = MaybeUninit::uninit();
1052 VectorscanRuntimeError::from_native(unsafe {
1053 hs::hs_deserialize_database(self.as_ptr(), self.len(), deserialized.as_mut_ptr())
1054 })?;
1055 let deserialized = unsafe { deserialized.assume_init() };
1056 Ok(unsafe { Database::from_native(deserialized) })
1057 }
1058
1059 /// Return the size of the allocation necessary for a subsequent call to
1060 /// [`Self::deserialize_db_at()`].
1061 pub fn deserialized_size(&self) -> Result<usize, VectorscanRuntimeError> {
1062 let mut deserialized_size: MaybeUninit<usize> = MaybeUninit::uninit();
1063 VectorscanRuntimeError::from_native(unsafe {
1064 hs::hs_serialized_database_size(self.as_ptr(), self.len(), deserialized_size.as_mut_ptr())
1065 })?;
1066 let deserialized_size = unsafe { deserialized_size.assume_init() };
1067 Ok(deserialized_size)
1068 }
1069
1070 /// Like [`Self::deserialize_db()`], but points into an existing allocation
1071 /// instead of making a new allocation.
1072 ///
1073 /// # Safety
1074 /// `db` must point to an allocation at least
1075 /// [`Self::deserialized_size()`] bytes in size!
1076 ///
1077 ///```
1078 /// #[cfg(feature = "compiler")]
1079 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
1080 /// use vectorscan::{expression::*, flags::*, database::*};
1081 /// use std::mem;
1082 ///
1083 /// let expr: Expression = "a+".parse()?;
1084 /// let serialized_db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?.serialize()?;
1085 ///
1086 /// // Allocate a vector with sufficient capacity for the deserialized db:
1087 /// let mut db_data: Vec<u8> = Vec::with_capacity(serialized_db.deserialized_size()?);
1088 /// let db = unsafe {
1089 /// let db_ptr: *mut NativeDb = mem::transmute(db_data.as_mut_ptr());
1090 /// serialized_db.deserialize_db_at(db_ptr)?;
1091 /// // Wrap in ManuallyDrop to avoid freeing memory owned by the `db_data` vector.
1092 /// mem::ManuallyDrop::new(Database::from_native(db_ptr))
1093 /// };
1094 /// // Note that the expected deserialized size is the same
1095 /// // as the resulting in-memory database size:
1096 /// assert_eq!(db.database_size()?, serialized_db.deserialized_size()?);
1097 /// Ok(())
1098 /// }
1099 /// # #[cfg(not(feature = "compiler"))]
1100 /// # fn main() {}
1101 /// ```
1102 pub unsafe fn deserialize_db_at(&self, db: *mut NativeDb) -> Result<(), VectorscanRuntimeError> {
1103 VectorscanRuntimeError::from_native(hs::hs_deserialize_database_at(
1104 self.as_ptr(),
1105 self.len(),
1106 db,
1107 ))
1108 }
1109
1110 /// Extract metadata about the serialized database into a new string
1111 /// allocation.
1112 ///
1113 ///```
1114 /// #[cfg(feature = "compiler")]
1115 /// fn main() -> Result<(), vectorscan::error::VectorscanError> {
1116 /// use vectorscan::{expression::*, flags::*};
1117 ///
1118 /// let expr: Expression = "a+".parse()?;
1119 /// let serialized_db = expr.compile(Flags::default(), Mode::BLOCK)?.serialize()?;
1120 /// let info = serialized_db.extract_db_info()?;
1121 /// assert_eq!(info.as_str(), "Version: 5.4.11 Features: AVX2 Mode: BLOCK");
1122 /// // Info is the same as would have been provided from deserializing:
1123 /// assert_eq!(info, serialized_db.deserialize_db()?.info()?);
1124 /// Ok(())
1125 /// }
1126 /// # #[cfg(not(feature = "compiler"))]
1127 /// # fn main() {}
1128 /// ```
1129 pub fn extract_db_info(&self) -> Result<DbInfo, VectorscanRuntimeError> {
1130 let mut info = ptr::null_mut();
1131 VectorscanRuntimeError::from_native(unsafe {
1132 hs::hs_serialized_database_info(self.as_ptr(), self.len(), &mut info)
1133 })?;
1134 let len = unsafe { CStr::from_ptr(info) }.to_bytes_with_nul().len();
1135 assert!(len > 0);
1136
1137 let ret = alloc::MiscAllocation {
1138 data: info as *mut u8,
1139 len,
1140 };
1141
1142 Ok(DbInfo(ret))
1143 }
1144}
1145
1146/// # Owned Allocations
1147/// Methods that produce new owned (`'static`) allocations.
1148///
1149/// A [`Clone`] impl is also available for such owned allocations.
1150impl SerializedDb<'static> {
1151 /// Write a serialized representation of `db` into a newly allocated region of
1152 /// memory.
1153 pub fn serialize_db(db: &Database) -> Result<Self, VectorscanRuntimeError> {
1154 let mut data = ptr::null_mut();
1155 let mut len: usize = 0;
1156
1157 VectorscanRuntimeError::from_native(unsafe {
1158 hs::hs_serialize_database(db.as_ref_native(), &mut data, &mut len)
1159 })?;
1160
1161 let data = data as *mut u8;
1162
1163 Ok(Self(alloc::DbAllocation::Misc(alloc::MiscAllocation {
1164 data,
1165 len,
1166 })))
1167 }
1168
1169 /// Allocate a new region of memory and copy over the referenced data from
1170 /// `s`.
1171 pub fn from_cloned_data(s: &SerializedDb) -> Self {
1172 let SerializedDb(ref s) = s;
1173 Self(alloc::DbAllocation::from_cloned_data(s))
1174 }
1175}
1176
1177impl Clone for SerializedDb<'static> {
1178 fn clone(&self) -> Self { Self::from_cloned_data(self) }
1179}
1180
1181/// Compile chimera state machines from expressions.
1182///
1183/// Unlike the base vectorscan library, chimera does not support database
1184/// serialization, so new [`chimera::ChimeraDb`] instances can only be created
1185/// by compiling them. That is why the `"chimera"` feature for this crate
1186/// requires the `"compiler"` feature.
1187#[cfg(feature = "chimera")]
1188#[cfg_attr(docsrs, doc(cfg(feature = "chimera")))]
1189pub mod chimera {
1190 use super::alloc::chimera::ChimeraMiscAllocation;
1191 #[cfg(feature = "compiler")]
1192 use super::Platform;
1193 #[cfg(feature = "compiler")]
1194 use crate::{
1195 error::chimera::ChimeraCompileError,
1196 expression::chimera::{ChimeraExpression, ChimeraExpressionSet, ChimeraMatchLimits},
1197 flags::chimera::{ChimeraFlags, ChimeraMode},
1198 };
1199 use crate::{error::chimera::ChimeraRuntimeError, hs, state::chimera::ChimeraScratch};
1200
1201 use std::{cmp, ffi::CStr, fmt, hash, mem, ops, ptr, slice, str};
1202
1203 /// Pointer type for chimera db allocations used in [`ChimeraDb#Managing
1204 /// Allocations`](ChimeraDb#managing-allocations).
1205 pub type NativeChimeraDb = hs::ch_database;
1206
1207 /// Read-only description of an in-memory PCRE state machine.
1208 ///
1209 /// This type also serves as the entry point to the various types of [pattern
1210 /// compilers](#pattern-compilers), including single expressions and
1211 /// expression sets.
1212 #[derive(Debug)]
1213 #[repr(transparent)]
1214 pub struct ChimeraDb(*mut NativeChimeraDb);
1215
1216 /// # Convenience Methods
1217 /// These methods prepare some resource within a new heap allocation and are
1218 /// useful for doctests and examples.
1219 ///
1220 /// ## Scratch Setup
1221 /// Databases already require their own heap allocation, which can be managed
1222 /// with the methods in [Managing Allocations](#managing-allocations).
1223 /// However, databases also impose a sort of implicit dynamic lifetime
1224 /// constraint on [`ChimeraScratch`] objects, which must be initialized
1225 /// against a db with [`ChimeraScratch::setup_for_db()`] before vectorscan
1226 /// can do any searching.
1227 ///
1228 /// It is encouraged to re-use [`ChimeraScratch`] objects across databases
1229 /// where possible to minimize unnecessary allocations, but
1230 /// [`Self::allocate_scratch()`] is provided as a convenience method to
1231 /// quickly produce a 1:1 db:scratch mapping.
1232 impl ChimeraDb {
1233 /// Call [`ChimeraScratch::setup_for_db()`] on a newly allocated
1234 /// [`ChimeraScratch::blank()`].
1235 ///
1236 ///```
1237 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1238 /// use vectorscan::{expression::chimera::*, flags::chimera::*, matchers::chimera::*};
1239 ///
1240 /// let expr: ChimeraExpression = "a+".parse()?;
1241 /// let db = expr.compile(ChimeraFlags::default(), ChimeraMode::NOGROUPS)?;
1242 /// let mut scratch = db.allocate_scratch()?;
1243 ///
1244 /// let mut matches: Vec<&str> = Vec::new();
1245 /// let e = |_| ChimeraMatchResult::Continue;
1246 /// scratch
1247 /// .scan_sync(&db, "aardvark".into(), |ChimeraMatch { source, .. }| {
1248 /// matches.push(unsafe { source.as_str() });
1249 /// ChimeraMatchResult::Continue
1250 /// }, e)?;
1251 /// assert_eq!(&matches, &["aa", "a"]);
1252 /// # Ok(())
1253 /// # }
1254 /// ```
1255 pub fn allocate_scratch(&self) -> Result<ChimeraScratch, ChimeraRuntimeError> {
1256 let mut scratch = ChimeraScratch::blank();
1257 scratch.setup_for_db(self)?;
1258 Ok(scratch)
1259 }
1260 }
1261
1262 /// # Pattern Compilers
1263 /// Chimera supports compiling state machines for single PCRE pattern strings
1264 /// as well as parallel sets of those patterns. Each compile method currently
1265 /// supports all [`ChimeraFlags`] arguments.
1266 ///
1267 /// ## Platform Compatibility
1268 /// Each method also accepts an optional [`Platform`] object,
1269 /// which is used to select processor features to compile the database for.
1270 /// While the default of [`None`] will enable all features available to the
1271 /// current processor, some features can be disabled in order to produce a
1272 /// database which can execute on a wider variety of target platforms.
1273 /// **However, note that since chimera does not support deserialization like
1274 /// the base vectorscan library, there currently seems to be no real benefit
1275 /// to a more-generic but less-performant compiled database, so using
1276 /// [`None`] is recommended in all cases.**
1277 ///
1278 ///```
1279 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1280 /// use vectorscan::{expression::chimera::*, flags::{*, chimera::*, platform::*}, database::chimera::*};
1281 ///
1282 /// let expr: ChimeraExpression = "a+".parse()?;
1283 ///
1284 /// // Verify that the current platform has AVX2 instructions, and make a db:
1285 /// let plat = Platform::local()?;
1286 /// assert!(plat.cpu_features.contains(&CpuFeatures::AVX2));
1287 /// assert!(plat != Platform::GENERIC);
1288 /// let db_with_avx2 = ChimeraDb::compile(
1289 /// &expr,
1290 /// ChimeraFlags::default(),
1291 /// ChimeraMode::NOGROUPS,
1292 /// Some(&plat),
1293 /// )?;
1294 ///
1295 /// // The only specialized instructions we have available are AVX2:
1296 /// assert_eq!(CpuFeatures::NONE, plat.cpu_features & !CpuFeatures::AVX2);
1297 /// // Avoid using AVX2 instructions:
1298 /// let db_no_avx2 = ChimeraDb::compile(
1299 /// &expr,
1300 /// ChimeraFlags::default(),
1301 /// ChimeraMode::NOGROUPS,
1302 /// Some(&Platform::GENERIC),
1303 /// )?;
1304 ///
1305 /// // Instruction selection does not affect the size of the state machine:
1306 /// assert!(db_with_avx2.database_size()? == db_no_avx2.database_size()?);
1307 ///
1308 /// // Now create a db with None for the platform:
1309 /// let db_local = ChimeraDb::compile(
1310 /// &expr,
1311 /// ChimeraFlags::default(),
1312 /// ChimeraMode::NOGROUPS,
1313 /// None,
1314 /// )?;
1315 /// assert!(db_with_avx2.database_size()? == db_local.database_size()?);
1316 ///
1317 /// // Using None produces the same type of db as Platform::local():
1318 /// assert!(db_with_avx2.info()? == db_local.info()?);
1319 /// assert!(db_no_avx2.info()? != db_local.info()?);
1320 /// # Ok(())
1321 /// # }
1322 /// ```
1323 ///
1324 /// ## Dynamic Memory Allocation
1325 /// These methods allocate a new region of memory using the db allocator
1326 /// (which can be overridden with
1327 /// [`crate::alloc::chimera::set_chimera_db_allocator()`]). That allocation
1328 /// can be manipulated as described in [Managing Allocations](#
1329 /// managing-allocations).
1330 #[cfg(feature = "compiler")]
1331 #[cfg_attr(docsrs, doc(cfg(feature = "compiler")))]
1332 impl ChimeraDb {
1333 /// Single pattern compiler.
1334 ///
1335 /// # Accepted Flags
1336 /// - [`CASELESS`](ChimeraFlags::CASELESS)
1337 /// - [`DOTALL`](ChimeraFlags::DOTALL)
1338 /// - [`MULTILINE`](ChimeraFlags::MULTILINE)
1339 /// - [`SINGLEMATCH`](ChimeraFlags::SINGLEMATCH)
1340 /// - [`UTF8`](ChimeraFlags::UTF8)
1341 /// - [`UCP`](ChimeraFlags::UCP)
1342 ///
1343 ///```
1344 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1345 /// use vectorscan::{expression::chimera::*, flags::chimera::*, database::chimera::*, matchers::chimera::*};
1346 ///
1347 /// let expr: ChimeraExpression = "hell(o)?".parse()?;
1348 /// let db = ChimeraDb::compile(&expr, ChimeraFlags::default(), ChimeraMode::GROUPS, None)?;
1349 ///
1350 /// let mut scratch = db.allocate_scratch()?;
1351 ///
1352 /// let mut matches: Vec<(&str, Option<&str>)> = Vec::new();
1353 /// let e = |_| ChimeraMatchResult::Continue;
1354 /// scratch
1355 /// .scan_sync(&db, "hello".into(), |m| {
1356 /// matches.push(unsafe { (
1357 /// m.source.as_str(),
1358 /// m.captures.unwrap()[1].map(|c| c.as_str()),
1359 /// ) });
1360 /// ChimeraMatchResult::Continue
1361 /// }, e)?;
1362 /// assert_eq!(&matches, &[("hello", Some("o"))]);
1363 /// # Ok(())
1364 /// # }
1365 /// ```
1366 pub fn compile(
1367 expression: &ChimeraExpression,
1368 flags: ChimeraFlags,
1369 mode: ChimeraMode,
1370 platform: Option<&Platform>,
1371 ) -> Result<Self, ChimeraCompileError> {
1372 let mut db = ptr::null_mut();
1373 let mut compile_err = ptr::null_mut();
1374 let platform: Option<hs::hs_platform_info> = platform.cloned().map(Platform::into_native);
1375 ChimeraRuntimeError::copy_from_native_compile_error(
1376 unsafe {
1377 hs::ch_compile(
1378 expression.as_ptr(),
1379 flags.into_native(),
1380 mode.into_native(),
1381 platform
1382 .as_ref()
1383 .map(|p| p as *const hs::hs_platform_info)
1384 .unwrap_or(ptr::null()),
1385 &mut db,
1386 &mut compile_err,
1387 )
1388 },
1389 compile_err,
1390 )?;
1391 Ok(unsafe { Self::from_native(db) })
1392 }
1393
1394 /// Multiple pattern compiler.
1395 ///
1396 /// # Accepted Flags
1397 /// - [`CASELESS`](ChimeraFlags::CASELESS)
1398 /// - [`DOTALL`](ChimeraFlags::DOTALL)
1399 /// - [`MULTILINE`](ChimeraFlags::MULTILINE)
1400 /// - [`SINGLEMATCH`](ChimeraFlags::SINGLEMATCH)
1401 /// - [`UTF8`](ChimeraFlags::UTF8)
1402 /// - [`UCP`](ChimeraFlags::UCP)
1403 ///
1404 ///```
1405 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1406 /// use vectorscan::{expression::{*, chimera::*}, flags::chimera::*, database::chimera::*, matchers::chimera::*};
1407 ///
1408 /// let a_expr: ChimeraExpression = "a+".parse()?;
1409 /// let b_expr: ChimeraExpression = "b+".parse()?;
1410 /// let exprs = ChimeraExpressionSet::from_exprs([&a_expr, &b_expr])
1411 /// .with_ids([ExprId(1), ExprId(2)])
1412 /// .with_limits(ChimeraMatchLimits { match_limit: 30, match_limit_recursion: 30 });
1413 /// let db = ChimeraDb::compile_multi(&exprs, ChimeraMode::NOGROUPS, None)?;
1414 /// let mut scratch = db.allocate_scratch()?;
1415 ///
1416 /// let mut matches: Vec<&str> = Vec::new();
1417 /// let e = |_| ChimeraMatchResult::Continue;
1418 /// scratch.scan_sync(&db, "aardvark imbibbe".into(), |ChimeraMatch { source, .. }| {
1419 /// matches.push(unsafe { source.as_str() });
1420 /// ChimeraMatchResult::Continue
1421 /// }, e)?;
1422 /// assert_eq!(&matches, &["aa", "a", "b", "bb"]);
1423 /// # Ok(())
1424 /// # }
1425 /// ```
1426 pub fn compile_multi(
1427 exprs: &ChimeraExpressionSet,
1428 mode: ChimeraMode,
1429 platform: Option<&Platform>,
1430 ) -> Result<Self, ChimeraCompileError> {
1431 let mut db = ptr::null_mut();
1432 let mut compile_err = ptr::null_mut();
1433 let platform: Option<hs::hs_platform_info> = platform.cloned().map(Platform::into_native);
1434 ChimeraRuntimeError::copy_from_native_compile_error(
1435 unsafe {
1436 if let Some(ChimeraMatchLimits {
1437 match_limit,
1438 match_limit_recursion,
1439 }) = exprs.limits()
1440 {
1441 hs::ch_compile_ext_multi(
1442 exprs.expressions_ptr(),
1443 exprs.flags_ptr(),
1444 exprs.ids_ptr(),
1445 exprs.num_elements(),
1446 mode.into_native(),
1447 match_limit,
1448 match_limit_recursion,
1449 platform
1450 .as_ref()
1451 .map(|p| p as *const hs::hs_platform_info)
1452 .unwrap_or(ptr::null()),
1453 &mut db,
1454 &mut compile_err,
1455 )
1456 } else {
1457 hs::ch_compile_multi(
1458 exprs.expressions_ptr(),
1459 exprs.flags_ptr(),
1460 exprs.ids_ptr(),
1461 exprs.num_elements(),
1462 mode.into_native(),
1463 platform
1464 .as_ref()
1465 .map(|p| p as *const hs::hs_platform_info)
1466 .unwrap_or(ptr::null()),
1467 &mut db,
1468 &mut compile_err,
1469 )
1470 }
1471 },
1472 compile_err,
1473 )?;
1474 Ok(unsafe { Self::from_native(db) })
1475 }
1476 }
1477
1478 /// # Introspection
1479 /// These methods extract various bits of runtime information from the db.
1480 impl ChimeraDb {
1481 /// Return the size of the db allocation.
1482 ///
1483 /// Using [`ChimeraFlags::UCP`] explodes the size of character classes,
1484 /// which increases the size of the state machine:
1485 ///
1486 ///```
1487 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1488 /// use vectorscan::{expression::chimera::*, flags::chimera::*};
1489 ///
1490 /// let expr: ChimeraExpression = r"\w".parse()?;
1491 /// let utf8_db = expr.compile(
1492 /// ChimeraFlags::UTF8 | ChimeraFlags::UCP,
1493 /// ChimeraMode::NOGROUPS,
1494 /// )?;
1495 /// let ascii_db = expr.compile(ChimeraFlags::default(), ChimeraMode::NOGROUPS)?;
1496 ///
1497 /// // Including UTF-8 classes increases the size:
1498 /// assert!(utf8_db.database_size()? > ascii_db.database_size()?);
1499 /// # Ok(())
1500 /// # }
1501 /// ```
1502 ///
1503 /// This size corresponds to the requested allocation size passed to the db
1504 /// allocator:
1505 ///
1506 ///```
1507 /// #[cfg(feature = "alloc")]
1508 /// fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1509 /// use vectorscan::{expression::chimera::*, flags::chimera::*, alloc::{*, chimera::*}};
1510 /// use std::alloc::System;
1511 ///
1512 /// // Wrap the standard Rust System allocator.
1513 /// let tracker = LayoutTracker::new(System.into());
1514 /// // Register it as the allocator for databases.
1515 /// assert!(set_chimera_db_allocator(tracker)?.is_none());
1516 ///
1517 /// let expr: ChimeraExpression = r"\w".parse()?;
1518 /// let utf8_db = expr.compile(
1519 /// ChimeraFlags::UTF8 | ChimeraFlags::UCP,
1520 /// ChimeraMode::NOGROUPS,
1521 /// )?;
1522 ///
1523 /// // Get the database allocator we just registered and view its live allocations:
1524 /// let allocs = get_chimera_db_allocator().as_ref().unwrap().current_allocations();
1525 /// // Verify that only the single known db was allocated:
1526 /// assert_eq!(1, allocs.len());
1527 /// let (_p, layout) = allocs[0];
1528 ///
1529 /// // Verify that the allocation size is the same as reported:
1530 /// assert_eq!(layout.size(), utf8_db.database_size()?);
1531 /// Ok(())
1532 /// }
1533 /// # #[cfg(not(feature = "alloc"))]
1534 /// # fn main() {}
1535 /// ```
1536 pub fn database_size(&self) -> Result<usize, ChimeraRuntimeError> {
1537 let mut database_size: usize = 0;
1538 ChimeraRuntimeError::from_native(unsafe {
1539 hs::ch_database_size(self.as_ref_native(), &mut database_size)
1540 })?;
1541 Ok(database_size)
1542 }
1543
1544 /// Extract metadata about the current database into a new string
1545 /// allocation.
1546 ///
1547 /// This is a convenience method that simply calls
1548 /// [`ChimeraDbInfo::extract_db_info()`].
1549 ///
1550 ///```
1551 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1552 /// use vectorscan::{expression::chimera::*, flags::chimera::*, database::chimera::*};
1553 ///
1554 /// let expr: ChimeraExpression = "a+".parse()?;
1555 /// let db = expr.compile(ChimeraFlags::default(), ChimeraMode::NOGROUPS)?;
1556 /// let info = ChimeraDbInfo::extract_db_info(&db)?;
1557 /// assert_eq!(info.as_str(), "Chimera Version: 5.4.11 Features: AVX2 Mode: BLOCK");
1558 /// # Ok(())
1559 /// # }
1560 /// ```
1561 pub fn info(&self) -> Result<ChimeraDbInfo, ChimeraRuntimeError> {
1562 ChimeraDbInfo::extract_db_info(self)
1563 }
1564 }
1565
1566 /// # Managing Allocations
1567 /// These methods provide access to the underlying memory allocation
1568 /// containing the data for the in-memory state machine. They can be used to
1569 /// control the memory location used for the state machine, or to preserve
1570 /// db allocations across weird lifetime constraints.
1571 ///
1572 /// Note that [`Self::database_size()`] can be used to determine the size of
1573 /// the memory allocation pointed to by [`Self::as_ref_native()`] and
1574 /// [`Self::as_mut_native()`].
1575 impl ChimeraDb {
1576 /// Wrap the provided allocation `p`.
1577 ///
1578 /// # Safety
1579 /// The pointer `p` must point to an initialized db allocation prepared by
1580 /// one of the compile methods.
1581 ///
1582 /// This method also makes it especially easy to create multiple references
1583 /// to the same allocation, which will then cause a double free when
1584 /// [`Self::try_drop()`] is called more than once for the same db
1585 /// allocation. To avoid this, wrap the result in a
1586 /// [`ManuallyDrop`](mem::ManuallyDrop):
1587 ///
1588 ///```
1589 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
1590 /// use vectorscan::{expression::chimera::*, flags::chimera::*, matchers::chimera::*, database::chimera::*, state::chimera::*};
1591 /// use std::mem::ManuallyDrop;
1592 ///
1593 /// // Compile a legitimate db:
1594 /// let expr: ChimeraExpression = "a+".parse()?;
1595 /// let mut db = expr.compile(ChimeraFlags::default(), ChimeraMode::NOGROUPS)?;
1596 ///
1597 /// // Create two new references to that allocation,
1598 /// // wrapped to avoid calling the drop code:
1599 /// let db_ptr: *mut NativeChimeraDb = db.as_mut_native();
1600 /// let db_ref_1 = ManuallyDrop::new(unsafe { ChimeraDb::from_native(db_ptr) });
1601 /// let db_ref_2 = ManuallyDrop::new(unsafe { ChimeraDb::from_native(db_ptr) });
1602 ///
1603 /// // Both db references are valid and can be used for matching.
1604 /// let mut scratch = ChimeraScratch::blank();
1605 /// scratch.setup_for_db(&db_ref_1)?;
1606 /// scratch.setup_for_db(&db_ref_2)?;
1607 ///
1608 /// let mut matches: Vec<&str> = Vec::new();
1609 /// let e = |_| ChimeraMatchResult::Continue;
1610 /// scratch
1611 /// .scan_sync(&db_ref_1, "aardvark".into(), |ChimeraMatch { source, .. }| {
1612 /// matches.push(unsafe { source.as_str() });
1613 /// ChimeraMatchResult::Continue
1614 /// }, e)?;
1615 /// scratch
1616 /// .scan_sync(&db_ref_2, "aardvark".into(), |ChimeraMatch { source, .. }| {
1617 /// matches.push(unsafe { source.as_str() });
1618 /// ChimeraMatchResult::Continue
1619 /// }, e)?;
1620 /// assert_eq!(&matches, &["aa", "a", "aa", "a"]);
1621 /// # Ok(())
1622 /// # }
1623 /// ```
1624 pub const unsafe fn from_native(p: *mut NativeChimeraDb) -> Self { Self(p) }
1625
1626 /// Get a read-only reference to the db allocation.
1627 ///
1628 /// This method is mostly used internally and cast to a pointer to provide
1629 /// to the chimera native library methods.
1630 pub fn as_ref_native(&self) -> &NativeChimeraDb { unsafe { &*self.0 } }
1631
1632 /// Get a mutable reference to the db allocation.
1633 ///
1634 /// The result of this method can be cast to a pointer and provided to
1635 /// [`Self::from_native()`].
1636 pub fn as_mut_native(&mut self) -> &mut NativeChimeraDb { unsafe { &mut *self.0 } }
1637
1638 /// Free the underlying db allocation.
1639 ///
1640 /// # Safety
1641 /// This method must be called at most once over the lifetime of each db
1642 /// allocation. It is called by default on drop, so
1643 /// [`ManuallyDrop`](mem::ManuallyDrop) is recommended to wrap instances
1644 /// that reference external data in order to avoid attempting to free the
1645 /// referenced data.
1646 ///
1647 /// ## Only Frees Memory
1648 /// This method performs no processing other than freeing the allocated
1649 /// memory, so it can be skipped without leaking resources if the
1650 /// underlying [`NativeChimeraDb`] allocation is freed by some other means.
1651 pub unsafe fn try_drop(&mut self) -> Result<(), ChimeraRuntimeError> {
1652 ChimeraRuntimeError::from_native(hs::ch_free_database(self.as_mut_native()))
1653 }
1654 }
1655
1656 impl ops::Drop for ChimeraDb {
1657 fn drop(&mut self) {
1658 unsafe {
1659 self.try_drop().unwrap();
1660 }
1661 }
1662 }
1663
1664 /// Wrapper for allocated string data returned by [`ChimeraDb::info()`].
1665 pub struct ChimeraDbInfo(ChimeraMiscAllocation);
1666
1667 impl ChimeraDbInfo {
1668 const fn without_null(&self) -> impl slice::SliceIndex<[u8], Output=[u8]> {
1669 ..(self.0.len() - 1)
1670 }
1671
1672 /// Return a view of the allocated string data.
1673 ///
1674 /// Chimera will always return valid UTF-8 data for this string, so it skips
1675 /// the validity check. Note that the returned string does not include
1676 /// the trailing null byte allocated by the underlying chimera library.
1677 pub fn as_str(&self) -> &str {
1678 unsafe { str::from_utf8_unchecked(&self.0.as_slice()[self.without_null()]) }
1679 }
1680
1681 /// Write out metadata for `db` into a newly allocated region.
1682 pub fn extract_db_info(db: &ChimeraDb) -> Result<Self, ChimeraRuntimeError> {
1683 let mut info = ptr::null_mut();
1684 ChimeraRuntimeError::from_native(unsafe {
1685 hs::ch_database_info(db.as_ref_native(), &mut info)
1686 })?;
1687 let len = unsafe { CStr::from_ptr(info) }.to_bytes_with_nul().len();
1688 assert!(len > 0);
1689
1690 let ret = ChimeraMiscAllocation {
1691 data: unsafe { mem::transmute(info) },
1692 len,
1693 };
1694
1695 Ok(Self(ret))
1696 }
1697 }
1698
1699 impl fmt::Debug for ChimeraDbInfo {
1700 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1701 write!(f, "ChimeraDbInfo({:?})", self.as_str())
1702 }
1703 }
1704
1705 impl fmt::Display for ChimeraDbInfo {
1706 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_str()) }
1707 }
1708
1709 impl cmp::PartialEq for ChimeraDbInfo {
1710 fn eq(&self, other: &Self) -> bool { self.as_str().eq(other.as_str()) }
1711 }
1712
1713 impl cmp::Eq for ChimeraDbInfo {}
1714
1715 impl cmp::PartialOrd for ChimeraDbInfo {
1716 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) }
1717 }
1718
1719 impl cmp::Ord for ChimeraDbInfo {
1720 fn cmp(&self, other: &Self) -> cmp::Ordering { self.as_str().cmp(other.as_str()) }
1721 }
1722
1723 impl hash::Hash for ChimeraDbInfo {
1724 fn hash<H>(&self, state: &mut H)
1725 where H: hash::Hasher {
1726 self.as_str().hash(state);
1727 }
1728 }
1729}