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}