chumsky/
cache.rs

1//! Traits and types that allow parsers to be cached between invocations.
2//!
3//! # Example
4//!
5//! ```
6//! #![feature(lazy_cell)]
7//! use std::sync::{LazyLock, Arc};
8//! use chumsky::{prelude::*, cache::{Cache, Cached}};
9//!
10//! #[derive(Debug, PartialEq)]
11//! enum Token<'a> { Ident(&'a str), Int(u64) }
12//!
13//! #[derive(Default)]
14//! struct TokenParser;
15//! impl Cached for TokenParser {
16//!     type Parser<'a> = Arc<dyn Parser<'a, &'a str, Token<'a>, extra::Default> + Send + Sync + 'a>;
17//!
18//!     fn make_parser<'a>(self) -> Self::Parser<'a> {
19//!         let ident = text::ident().map(Token::Ident);
20//!         let num = text::int(10).from_str().unwrapped().map(Token::Int);
21//!         Arc::new(ident.or(num))
22//!     }
23//! }
24//!
25//! // The parser cache doesn't have a lifetime and so can be stored pretty much anywhere:
26//! static PARSER: LazyLock<Cache<TokenParser>> = LazyLock::new(Cache::default);
27//!
28//! // The parser can be used from any context simply by calling `.get()` on the cache
29//! assert_eq!(PARSER.get().parse("42").into_result(), Ok(Token::Int(42)));
30//! assert_eq!(PARSER.get().parse("hello").into_result(), Ok(Token::Ident("hello")));
31//! ```
32
33use super::*;
34
35/// Implementing this trait allows you to cache parsers for use with inputs of different lifetimes, avoiding the
36/// need to recreate the parser for each input lifetime.
37pub trait Cached {
38    /// The type of the parser to be cached.
39    ///
40    /// Because parsers tend to have unwieldy types, it is recommended to perform type erasure here. For example,
41    /// a parser with input type `&'src str` and output type `Token<'src>` might have one of the following types.
42    ///
43    /// ```ignore
44    /// Boxed<'src, 'src, &'src str, Token<'src>, extra::Default>
45    /// Arc<dyn Parser<'src, &'src str, Token<'src>, extra::Default> + Send + Sync + 'src>
46    /// ```
47    type Parser<'src>;
48
49    /// Create an instance of the parser
50    fn make_parser<'src>(self) -> Self::Parser<'src>;
51}
52
53/// Allows a parser to be cached for reuse with inputs and outputs of different lifetimes.
54pub struct Cache<C: Cached> {
55    parser: C::Parser<'static>,
56    #[allow(dead_code)]
57    phantom: EmptyPhantom<C>,
58}
59
60impl<C: Cached + Default> Default for Cache<C> {
61    fn default() -> Self {
62        Self::new(C::default())
63    }
64}
65
66impl<C: Cached> Cache<C> {
67    /// Create a new cached parser.
68    pub fn new(cacher: C) -> Self {
69        Self {
70            parser: cacher.make_parser(),
71            phantom: EmptyPhantom::new(),
72        }
73    }
74
75    /// Get a reference to the cached parser.
76    ///
77    /// Because this function is generic over an input lifetime, the returned parser can be used in many
78    /// different contexts.
79    pub fn get<'src>(&self) -> &C::Parser<'src> {
80        // SAFETY: This is safe because the API of `Cache` requires that the parser we store is bound by an arbitrary
81        // lifetime variable (see `Cached::make_parser`). Therefore, the implementor of `Cached` has no way to
82        // 'discover' the lifetime and so, because lifetimes are entirely removed during monomorphisation, the parser
83        // must be valid for arbitrary lifetimes.
84        unsafe { &*(&self.parser as *const C::Parser<'_>).cast() }
85    }
86}