1
Fork 0
mirror of https://github.com/RGBCube/cstree synced 2025-07-27 09:07:44 +00:00

Implement Resolver and Interner for Arc<MultiThreadedTokenInterner> (#72)

* Implement traits for Arc<MultiThreadedTokenInterner>

* implement Resolver for Arc<Resolver>, update docs
This commit is contained in:
RGBCube 2025-03-21 00:33:19 +03:00 committed by GitHub
parent e90300f4fc
commit fb8cc54104
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 66 additions and 33 deletions

View file

@ -2,6 +2,10 @@
## Unreleased
* `&I` and `&mut I` will now implement `Resolver` if `I` implements `Resolver`.
* `&mut I` will now implement `Interner` if `I` implements `Interner`.
* Added an implementation for `Arc<MultiThreadedTokenInterner>` to implement `Resolver` and `Interner` so an `Arc` may be used alternatively to a reference to share access to the interner.
## `v0.12.2`
* `Checkpoint`s for the `GreenNodeBuilder` can now be used across node boundaries, meaning you can use them to wrap (finished) nodes in addition to just tokens.

View file

@ -69,24 +69,28 @@ shared access. With the `multi_threaded_interning` feature, you can get such an
[`new_threaded_interner`]. The feature also enables support for `ThreadedRodeo`, the multi-threaded interner from
`lasso`.
**You can pass a reference to that interner to anything that expects an [`Interner`]!**
**You can pass a reference or an Arc to that interner to anything that expects an [`Interner`]!**
While the interning methods on [`Interner`] require a `&mut self` to also work for single-threaded interners, both
[`Resolver`] and [`Interner`] will be implemented for `&interner` if `interner` is multi-threaded:
[`Resolver`] and [`Interner`] will be implemented for `&interner` and `Arc::new(interner)` if `interner` is multi-threaded:
```
# use cstree::testing::*;
# use cstree::interning::*;
let interner = new_threaded_interner();
let mut builder: GreenNodeBuilder<MySyntax, &MultiThreadedTokenInterner> =
GreenNodeBuilder::from_interner(&interner);
# use std::sync::Arc;
let interner = Arc::new(new_threaded_interner());
let mut builder: GreenNodeBuilder<MySyntax, Arc<MultiThreadedTokenInterner>> =
GreenNodeBuilder::from_interner(Arc::clone(&interner));
// or:
// let interner = new_threaded_interner();
// let mut builder: GreenNodeBuilder<MySyntax, &MultiThreadedTokenInterner> =
// GreenNodeBuilder::from_interner(&interner);
# builder.start_node(Root);
# builder.token(Int, "42");
# builder.finish_node();
parse(&mut builder, "42");
let (tree, cache) = builder.finish();
// Note that we get a cache and interner back, because we passed an "owned" reference to `from_interner`
let used_interner = cache.unwrap().into_interner().unwrap();
assert_eq!(used_interner as *const _, &interner as *const _);
let int = tree.children().next().unwrap();
assert_eq!(int.as_token().unwrap().text(&interner), Some("42"));
```
@ -177,8 +181,9 @@ pub fn new_interner() -> TokenInterner {
/// Constructs a new [`Interner`] that can be used across multiple threads.
///
/// Note that you can use `&MultiThreadTokenInterner` to access interning methods through a shared reference, as well as
/// construct new syntax trees. See [the module documentation](self) for more information and examples.
/// Note that you can use `&MultiThreadedTokenInterner` and `Arc<MultiThreadTokenInterner>` to access interning methods
/// through a shared reference, as well as construct new syntax trees. See [the module documentation](self) for more
/// information and examples.
#[cfg(feature = "multi_threaded_interning")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "multi_threaded_interning")))]
#[inline]

View file

@ -1,6 +1,7 @@
#![cfg(not(feature = "lasso_compat"))]
use core::fmt;
use std::sync::Arc as StdArc;
use indexmap::IndexSet;
use rustc_hash::FxBuildHasher;
@ -43,6 +44,13 @@ impl Resolver<TokenKey> for TokenInterner {
}
}
impl Resolver<TokenKey> for StdArc<TokenInterner> {
fn try_resolve(&self, key: TokenKey) -> Option<&str> {
let index = key.into_u32() as usize;
self.id_set.get_index(index).map(String::as_str)
}
}
// `TokenKey` can represent `1` to `u32::MAX` (due to the `NonNull` niche), so `u32::MAX` elements.
// Set indices start at 0, so everything shifts down by 1.
const N_INDICES: usize = u32::MAX as usize;

View file

@ -87,10 +87,12 @@ pub use multi_threaded::MultiThreadedTokenInterner;
mod multi_threaded {
use super::*;
use std::sync::Arc as StdArc;
/// A threadsafe [`Interner`] for deduplicating [`GreenToken`](crate::green::GreenToken) strings.
///
/// Note that [`Interner`] and [`Resolver`] are also implemented for `&MultiThreadTokenInterner` so you can pass
/// `&mut &interner` in shared contexts.
/// Note that [`Interner`] and [`Resolver`] are also implemented for `&MultiThreadTokenInterner` and
/// `Arc<MultiThreadTokenInterner>` so you can pass a mutable reference to either of these in shared contexts.
#[cfg_attr(doc_cfg, doc(cfg(feature = "multi_threaded_interning")))]
pub struct MultiThreadedTokenInterner {
rodeo: ThreadedRodeo<TokenKey, FxBuildHasher>,
@ -114,6 +116,5 @@ mod multi_threaded {
}
impl_traits!(for MultiThreadedTokenInterner, if #[cfg(feature = "multi_threaded_interning")]);
impl_traits!(for &MultiThreadedTokenInterner, if #[cfg(feature = "multi_threaded_interning")]);
impl_traits!(for StdArc<MultiThreadedTokenInterner>, if #[cfg(feature = "multi_threaded_interning")]);
}

View file

@ -119,23 +119,6 @@ mod multi_threaded {
compat_interner!(ThreadedRodeo<K, S> where K: Hash, S: Clone if #[cfg(feature = "multi_threaded_interning")]);
#[cfg_attr(doc_cfg, doc(cfg(feature = "multi_threaded_interning")))]
impl<K, S> Resolver<TokenKey> for &lasso::ThreadedRodeo<K, S>
where
K: lasso::Key + Hash,
S: BuildHasher + Clone,
{
#[inline]
fn try_resolve(&self, key: TokenKey) -> Option<&str> {
<lasso::ThreadedRodeo<K, S> as Resolver<TokenKey>>::try_resolve(self, key)
}
#[inline]
fn resolve(&self, key: TokenKey) -> &str {
<lasso::ThreadedRodeo<K, S> as Resolver<TokenKey>>::resolve(self, key)
}
}
#[cfg_attr(doc_cfg, doc(cfg(feature = "multi_threaded_interning")))]
impl<K, S> Interner<TokenKey> for &lasso::ThreadedRodeo<K, S>
where

View file

@ -38,13 +38,33 @@ pub trait Resolver<Key: InternKey = TokenKey> {
}
}
impl<R: Resolver> Resolver for &R {
fn try_resolve(&self, key: TokenKey) -> Option<&str> {
(**self).try_resolve(key)
}
fn resolve(&self, key: TokenKey) -> &str {
(**self).resolve(key)
}
}
impl<R: Resolver> Resolver for &mut R {
fn try_resolve(&self, key: TokenKey) -> Option<&str> {
(**self).try_resolve(key)
}
fn resolve(&self, key: TokenKey) -> &str {
(**self).resolve(key)
}
}
/// A full interner, which can intern new strings returning intern keys and also resolve intern keys to the interned
/// value.
///
/// **Note:** Because single-threaded interners may require mutable access, the methods on this trait take `&mut self`.
/// In order to use a multi- (or single)-threaded interner that allows access through a shared reference, it is
/// implemented for `&`[`MultiThreadedTokenInterner`](crate::interning::MultiThreadedTokenInterner), allowing it to be
/// used with a `&mut &MultiThreadTokenInterner`.
/// implemented for `&MultiThreadedTokenInterner` and `Arc<MultiThreadedTokenInterner>`, allowing it
/// to be used with a `&mut &MultiThreadedTokenInterner` and `&mut Arc<MultiThreadTokenInterner>`.
pub trait Interner<Key: InternKey = TokenKey>: Resolver<Key> {
/// Represents possible ways in which interning may fail.
/// For example, this might be running out of fresh intern keys, or failure to allocate sufficient space for a new
@ -65,3 +85,15 @@ pub trait Interner<Key: InternKey = TokenKey>: Resolver<Key> {
.unwrap_or_else(|_| panic!("failed to intern `{text:?}`"))
}
}
impl<I: Interner> Interner for &mut I {
type Error = I::Error;
fn try_get_or_intern(&mut self, text: &str) -> Result<TokenKey, Self::Error> {
(**self).try_get_or_intern(text)
}
fn get_or_intern(&mut self, text: &str) -> TokenKey {
(**self).get_or_intern(text)
}
}