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

be more flexible with NodeCache interner and GreenNodeBuilder cache

This commit is contained in:
Domenic Quirl 2021-08-28 10:58:10 +02:00
parent e3aa119a2b
commit 78e54d59db
11 changed files with 113 additions and 34 deletions

View file

@ -28,8 +28,8 @@ where
{ {
let mut builder = GreenNodeBuilder::with_cache(cache); let mut builder = GreenNodeBuilder::with_cache(cache);
build_recursive(root, &mut builder, 0); build_recursive(root, &mut builder, 0);
let (node, interner) = builder.finish(); let (node, cache) = builder.finish();
assert!(interner.is_none()); assert!(cache.is_none());
node node
} }

View file

@ -117,8 +117,11 @@ impl<'input, I: Iterator<Item = (SyntaxKind, &'input str)>> Parser<'input, I> {
self.parse_add(); self.parse_add();
self.builder.finish_node(); self.builder.finish_node();
let (tree, resolver) = self.builder.finish(); let (tree, cache) = self.builder.finish();
(SyntaxNode::new_root(tree), resolver.unwrap().into_resolver()) (
SyntaxNode::new_root(tree),
cache.unwrap().into_interner().unwrap().into_resolver(),
)
} }
} }

View file

@ -124,10 +124,10 @@ fn parse(text: &str) -> Parse<impl Resolver> {
// Get the green tree from the builder. // Get the green tree from the builder.
// Note that, since we didn't provide our own interner to the builder, it has // Note that, since we didn't provide our own interner to the builder, it has
// instantiated one for us and now returns it together with the tree. // instantiated one for us and now returns it together with the tree.
let (tree, interner) = self.builder.finish(); let (tree, cache) = self.builder.finish();
Parse { Parse {
green_node: tree, green_node: tree,
resolver: interner.unwrap().into_resolver(), resolver: cache.unwrap().into_interner().unwrap().into_resolver(),
errors: self.errors, errors: self.errors,
} }
} }

View file

@ -90,7 +90,9 @@ where
/// assert_eq!(tree.kind(), ROOT); /// assert_eq!(tree.kind(), ROOT);
/// let int = tree.children().next().unwrap(); /// let int = tree.children().next().unwrap();
/// assert_eq!(int.kind(), INT); /// assert_eq!(int.kind(), INT);
/// assert_eq!(int.as_token().unwrap().text(&interner), "42");
/// ``` /// ```
#[inline]
pub fn with_interner(interner: &'i mut I) -> Self { pub fn with_interner(interner: &'i mut I) -> Self {
Self { Self {
nodes: FxHashMap::default(), nodes: FxHashMap::default(),
@ -99,6 +101,47 @@ where
} }
} }
/// Constructs a new, empty cache that will use the given interner to deduplicate source text
/// (strings) across tokens.
/// # Examples
/// ```
/// # use cstree::*;
/// use lasso::Rodeo;
/// # const ROOT: SyntaxKind = SyntaxKind(0);
/// # const INT: SyntaxKind = SyntaxKind(1);
/// # fn parse(b: &mut GreenNodeBuilder<Rodeo>, s: &str) {}
/// let mut interner = Rodeo::new();
/// let cache = NodeCache::from_interner(interner);
/// let mut builder = GreenNodeBuilder::from_cache(cache);
/// # builder.start_node(ROOT);
/// # builder.token(INT, "42");
/// # builder.finish_node();
/// parse(&mut builder, "42");
/// let (tree, cache) = builder.finish();
/// let interner = cache.unwrap().into_interner().unwrap();
/// assert_eq!(tree.kind(), ROOT);
/// let int = tree.children().next().unwrap();
/// assert_eq!(int.kind(), INT);
/// assert_eq!(int.as_token().unwrap().text(&interner), "42");
/// ```
#[inline]
pub fn from_interner(interner: I) -> Self {
Self {
nodes: FxHashMap::default(),
tokens: FxHashMap::default(),
interner: MaybeOwned::Owned(interner),
}
}
/// If this node cache was constructed with [`new`](NodeCache::new) or
/// [`from_interner`](NodeCache::from_interner), returns the interner used to deduplicate source
/// text (strings) to allow resolving tree tokens back to text and re-using the interner to build
/// additonal trees.
#[inline]
pub fn into_interner(self) -> Option<I> {
self.interner.into_owned()
}
fn node(&mut self, kind: SyntaxKind, children: &[GreenElement]) -> GreenNode { fn node(&mut self, kind: SyntaxKind, children: &[GreenElement]) -> GreenNode {
let mut hasher = FxHasher32::default(); let mut hasher = FxHasher32::default();
let mut text_len: TextSize = 0.into(); let mut text_len: TextSize = 0.into();
@ -200,10 +243,11 @@ impl<T: Default> Default for MaybeOwned<'_, T> {
pub struct Checkpoint(usize); pub struct Checkpoint(usize);
/// A builder for green trees. /// A builder for green trees.
/// Construct with [`new`](GreenNodeBuilder::new) or [`with_cache`](GreenNodeBuilder::with_cache). To /// Construct with [`new`](GreenNodeBuilder::new), [`with_cache`](GreenNodeBuilder::with_cache), or
/// add tree nodes, start them with [`start_node`](GreenNodeBuilder::start_node), add /// [`from_cache`](GreenNodeBuilder::from_cache). To add tree nodes, start them with
/// [`token`](GreenNodeBuilder::token)s and then [`finish_node`](GreenNodeBuilder::finish_node). When /// [`start_node`](GreenNodeBuilder::start_node), add [`token`](GreenNodeBuilder::token)s and then
/// the whole tree is constructed, call [`finish`](GreenNodeBuilder::finish) to obtain the root. /// [`finish_node`](GreenNodeBuilder::finish_node). When the whole tree is constructed, call
/// [`finish`](GreenNodeBuilder::finish) to obtain the root.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -214,11 +258,11 @@ pub struct Checkpoint(usize);
/// builder.start_node(ROOT); /// builder.start_node(ROOT);
/// builder.token(INT, "42"); /// builder.token(INT, "42");
/// builder.finish_node(); /// builder.finish_node();
/// let (tree, interner) = builder.finish(); /// let (tree, cache) = builder.finish();
/// assert_eq!(tree.kind(), ROOT); /// assert_eq!(tree.kind(), ROOT);
/// let int = tree.children().next().unwrap(); /// let int = tree.children().next().unwrap();
/// assert_eq!(int.kind(), INT); /// assert_eq!(int.kind(), INT);
/// let resolver = interner.unwrap().into_resolver(); /// let resolver = cache.unwrap().into_interner().unwrap().into_resolver();
/// assert_eq!(int.as_token().unwrap().text(&resolver), "42"); /// assert_eq!(int.as_token().unwrap().text(&resolver), "42");
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
@ -259,6 +303,36 @@ where
} }
} }
/// Reusing a [`NodeCache`] between multiple builders saves memory, as it allows to structurally
/// share underlying trees.
/// The `cache` given will be returned on [`finish`](GreenNodeBuilder::finish).
/// # Examples
/// ```
/// # use cstree::*;
/// # const ROOT: SyntaxKind = SyntaxKind(0);
/// # const INT: SyntaxKind = SyntaxKind(1);
/// # fn parse(b: &mut GreenNodeBuilder, s: &str) {}
/// let cache = NodeCache::new();
/// let mut builder = GreenNodeBuilder::from_cache(cache);
/// # builder.start_node(ROOT);
/// # builder.token(INT, "42");
/// # builder.finish_node();
/// parse(&mut builder, "42");
/// let (tree, cache) = builder.finish();
/// let interner = cache.unwrap().into_interner().unwrap();
/// assert_eq!(tree.kind(), ROOT);
/// let int = tree.children().next().unwrap();
/// assert_eq!(int.kind(), INT);
/// assert_eq!(int.as_token().unwrap().text(&interner), "42");
/// ```
pub fn from_cache(cache: NodeCache<'interner, I>) -> Self {
Self {
cache: MaybeOwned::Owned(cache),
parents: Vec::with_capacity(8),
children: Vec::with_capacity(8),
}
}
/// Add new token to the current branch. /// Add new token to the current branch.
#[inline] #[inline]
pub fn token(&mut self, kind: SyntaxKind, text: &str) { pub fn token(&mut self, kind: SyntaxKind, text: &str) {
@ -340,15 +414,16 @@ where
/// [`finish_node`](GreenNodeBuilder::finish_node) are balanced, i.e. that every started node has /// [`finish_node`](GreenNodeBuilder::finish_node) are balanced, i.e. that every started node has
/// been completed! /// been completed!
/// ///
/// If this builder was constructed with [`new`](GreenNodeBuilder::new), this method returns the /// If this builder was constructed with [`new`](GreenNodeBuilder::new) or
/// interner used to deduplicate source text (strings) as its second return value to allow /// [`from_cache`](GreenNodeBuilder::from_cache), this method returns the cache used to deduplicate tree nodes
/// resolving tree tokens back to text and re-using the interner to build additonal trees. /// (strings) as its second return value to allow re-using the cache or extracting the underlying string
/// [`Interner`]. See also [`NodeCache::into_interner`].
#[inline] #[inline]
pub fn finish(mut self) -> (GreenNode, Option<I>) { pub fn finish(mut self) -> (GreenNode, Option<NodeCache<'interner, I>>) {
assert_eq!(self.children.len(), 1); assert_eq!(self.children.len(), 1);
let resolver = self.cache.into_owned().and_then(|cache| cache.interner.into_owned()); let cache = self.cache.into_owned();
match self.children.pop().unwrap() { match self.children.pop().unwrap() {
NodeOrToken::Node(node) => (node, resolver), NodeOrToken::Node(node) => (node, cache),
NodeOrToken::Token(_) => panic!("called `finish` on a `GreenNodeBuilder` which only contained a token"), NodeOrToken::Token(_) => panic!("called `finish` on a `GreenNodeBuilder` which only contained a token"),
} }
} }

View file

@ -191,8 +191,9 @@ where
} }
} }
let (tree, resolver) = builder.finish(); let (tree, cache) = builder.finish();
let tree = ResolvedNode::new_root_with_resolver(tree, resolver.unwrap().into_resolver()); let tree =
ResolvedNode::new_root_with_resolver(tree, cache.unwrap().into_interner().unwrap().into_resolver());
Ok((tree, data_indices)) Ok((tree, data_indices))
} }
} }

View file

@ -366,8 +366,8 @@ impl<L: Language, D> SyntaxNode<L, D> {
/// builder.start_node(ROOT); /// builder.start_node(ROOT);
/// builder.token(TOKEN, "content"); /// builder.token(TOKEN, "content");
/// builder.finish_node(); /// builder.finish_node();
/// let (green, resolver) = builder.finish(); /// let (green, cache) = builder.finish();
/// let root: ResolvedNode<Lang> = SyntaxNode::new_root_with_resolver(green, resolver.unwrap()); /// let root: ResolvedNode<Lang> = SyntaxNode::new_root_with_resolver(green, cache.unwrap().into_interner().unwrap());
/// assert_eq!(root.text(), "content"); /// assert_eq!(root.text(), "content");
/// ``` /// ```
#[inline] #[inline]

View file

@ -44,8 +44,8 @@ use crate::{interning::Resolver, Language, SyntaxNode, SyntaxToken, TextRange, T
/// # builder.start_node(LITERAL); /// # builder.start_node(LITERAL);
/// # builder.token(LITERAL, s); /// # builder.token(LITERAL, s);
/// # builder.finish_node(); /// # builder.finish_node();
/// # let (root, interner) = builder.finish(); /// # let (root, cache) = builder.finish();
/// # let resolver = interner.unwrap().into_resolver(); /// # let resolver = cache.unwrap().into_interner().unwrap().into_resolver();
/// # SyntaxNode::new_root_with_resolver(root, resolver) /// # SyntaxNode::new_root_with_resolver(root, resolver)
/// # } /// # }
/// let node = parse_float_literal("2.748E2"); /// let node = parse_float_literal("2.748E2");
@ -407,8 +407,8 @@ mod tests {
builder.token(SyntaxKind(92), chunk); builder.token(SyntaxKind(92), chunk);
} }
builder.finish_node(); builder.finish_node();
let (node, interner) = builder.finish(); let (node, cache) = builder.finish();
(SyntaxNode::new_root(node), interner.unwrap()) (SyntaxNode::new_root(node), cache.unwrap().into_interner().unwrap())
} }
#[test] #[test]

View file

@ -5,8 +5,8 @@ use lasso::{Resolver, Rodeo};
fn build_tree<D>(root: &Element<'_>) -> (SyntaxNode<D>, impl Resolver) { fn build_tree<D>(root: &Element<'_>) -> (SyntaxNode<D>, impl Resolver) {
let mut builder = GreenNodeBuilder::new(); let mut builder = GreenNodeBuilder::new();
build_recursive(root, &mut builder, 0); build_recursive(root, &mut builder, 0);
let (node, interner) = builder.finish(); let (node, cache) = builder.finish();
(SyntaxNode::new_root(node), interner.unwrap()) (SyntaxNode::new_root(node), cache.unwrap().into_interner().unwrap())
} }
fn two_level_tree() -> Element<'static> { fn two_level_tree() -> Element<'static> {

View file

@ -43,8 +43,8 @@ where
{ {
let mut builder = GreenNodeBuilder::with_cache(cache); let mut builder = GreenNodeBuilder::with_cache(cache);
build_recursive(root, &mut builder, 0); build_recursive(root, &mut builder, 0);
let (node, interner) = builder.finish(); let (node, cache) = builder.finish();
assert!(interner.is_none()); assert!(cache.is_none());
node node
} }

View file

@ -9,8 +9,8 @@ use cstree::{interning::IntoResolver, GreenNodeBuilder};
fn build_tree<D>(root: &Element<'_>) -> ResolvedNode<D> { fn build_tree<D>(root: &Element<'_>) -> ResolvedNode<D> {
let mut builder = GreenNodeBuilder::new(); let mut builder = GreenNodeBuilder::new();
build_recursive(root, &mut builder, 0); build_recursive(root, &mut builder, 0);
let (node, interner) = builder.finish(); let (node, cache) = builder.finish();
SyntaxNode::new_root_with_resolver(node, interner.unwrap().into_resolver()) SyntaxNode::new_root_with_resolver(node, cache.unwrap().into_interner().unwrap().into_resolver())
} }
fn two_level_tree() -> Element<'static> { fn two_level_tree() -> Element<'static> {

View file

@ -225,8 +225,8 @@ fn three_level_tree() -> Element<'static> {
fn build_tree(root: Element<'_>) -> ResolvedNode<String> { fn build_tree(root: Element<'_>) -> ResolvedNode<String> {
let mut builder = GreenNodeBuilder::new(); let mut builder = GreenNodeBuilder::new();
build_recursive(&root, &mut builder, 0); build_recursive(&root, &mut builder, 0);
let (node, interner) = builder.finish(); let (node, cache) = builder.finish();
SyntaxNode::new_root_with_resolver(node, interner.unwrap().into_resolver()) SyntaxNode::new_root_with_resolver(node, cache.unwrap().into_interner().unwrap().into_resolver())
} }
fn attach_data(node: &SyntaxNode<String>) { fn attach_data(node: &SyntaxNode<String>) {