diff --git a/examples/math.rs b/examples/math.rs index 6e56a81..072cebc 100644 --- a/examples/math.rs +++ b/examples/math.rs @@ -13,10 +13,7 @@ //! - "+" Token(Add) //! - "4" Token(Number) -use cstree::{ - interning::{Reader, Resolver}, - GreenNodeBuilder, NodeOrToken, -}; +use cstree::{interning::Resolver, GreenNodeBuilder, NodeOrToken}; use std::iter::Peekable; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -66,7 +63,7 @@ type SyntaxElement = cstree::NodeOrToken; type SyntaxElementRef<'a> = cstree::NodeOrToken<&'a SyntaxNode, &'a SyntaxToken>; struct Parser<'input, I: Iterator> { - builder: GreenNodeBuilder<'static>, + builder: GreenNodeBuilder<'static, 'static>, iter: Peekable, } impl<'input, I: Iterator> Parser<'input, I> { diff --git a/examples/s_expressions.rs b/examples/s_expressions.rs index 5142283..57fdc3d 100644 --- a/examples/s_expressions.rs +++ b/examples/s_expressions.rs @@ -62,10 +62,7 @@ impl cstree::Language for Lang { /// GreenNode is an immutable tree, which is cheap to change, /// but doesn't contain offsets and parent pointers. -use cstree::{ - interning::{Reader, Resolver}, - GreenNode, -}; +use cstree::{interning::Resolver, GreenNode}; /// You can construct GreenNodes by hand, but a builder /// is helpful for top-down parsers: it maintains a stack @@ -91,7 +88,7 @@ fn parse(text: &str) -> Parse { /// in *reverse* order. tokens: Vec<(SyntaxKind, &'input str)>, /// the in-progress tree. - builder: GreenNodeBuilder<'static>, + builder: GreenNodeBuilder<'static, 'static>, /// the list of syntax errors we've accumulated /// so far. errors: Vec, diff --git a/src/green/builder.rs b/src/green/builder.rs index 527d222..15875ab 100644 --- a/src/green/builder.rs +++ b/src/green/builder.rs @@ -19,29 +19,42 @@ use super::{node::GreenNodeHead, token::GreenTokenData}; const CHILDREN_CACHE_THRESHOLD: usize = 3; #[derive(Debug)] -pub struct NodeCache { +pub struct NodeCache<'i, I = Rodeo> { nodes: FxHashMap, tokens: FxHashMap, - interner: Rodeo, + interner: MaybeOwned<'i, I>, } -impl NodeCache { +impl NodeCache<'static, Rodeo> { pub fn new() -> Self { Self { nodes: FxHashMap::default(), tokens: FxHashMap::default(), - interner: Rodeo::with_capacity_and_hasher( + interner: MaybeOwned::Owned(Rodeo::with_capacity_and_hasher( // capacity values suggested by author of `lasso` Capacity::new(512, unsafe { NonZeroUsize::new_unchecked(4096) }), FxBuildHasher::default(), - ), + )), + } + } +} + +impl<'i, I> NodeCache<'i, I> +where + I: Interner, +{ + pub fn with_interner(interner: &'i mut I) -> Self { + Self { + nodes: FxHashMap::default(), + tokens: FxHashMap::default(), + interner: MaybeOwned::Borrowed(interner), } } - fn node(&mut self, kind: SyntaxKind, children: I) -> GreenNode + fn node(&mut self, kind: SyntaxKind, children: It) -> GreenNode where - I: IntoIterator, - I::IntoIter: ExactSizeIterator, + It: IntoIterator, + It::IntoIter: ExactSizeIterator, { let children = children.into_iter(); @@ -61,10 +74,10 @@ impl NodeCache { /// Creates a [`GreenNode`] by looking inside the cache or inserting /// a new node into the cache if it's a cache miss. - fn get_cached_node(&mut self, kind: SyntaxKind, children: I) -> GreenNode + fn get_cached_node(&mut self, kind: SyntaxKind, children: It) -> GreenNode where - I: IntoIterator, - I::IntoIter: ExactSizeIterator, + It: IntoIterator, + It::IntoIter: ExactSizeIterator, { #[derive(Clone)] struct ChildrenIter { @@ -132,6 +145,15 @@ enum MaybeOwned<'a, T> { Borrowed(&'a mut T), } +impl MaybeOwned<'_, T> { + fn as_owned(self) -> Option { + match self { + MaybeOwned::Owned(owned) => Some(owned), + MaybeOwned::Borrowed(_) => None, + } + } +} + impl std::ops::Deref for MaybeOwned<'_, T> { type Target = T; @@ -164,26 +186,31 @@ pub struct Checkpoint(usize); /// A builder for a green tree. #[derive(Debug)] -pub struct GreenNodeBuilder<'cache> { - cache: MaybeOwned<'cache, NodeCache>, +pub struct GreenNodeBuilder<'cache, 'interner, I = Rodeo> { + cache: MaybeOwned<'cache, NodeCache<'interner, I>>, parents: Vec<(SyntaxKind, usize)>, children: Vec, } -impl GreenNodeBuilder<'_> { +impl GreenNodeBuilder<'static, 'static, Rodeo> { /// Creates new builder. - pub fn new() -> GreenNodeBuilder<'static> { - GreenNodeBuilder { + pub fn new() -> Self { + Self { cache: MaybeOwned::Owned(NodeCache::new()), parents: Vec::with_capacity(8), children: Vec::with_capacity(8), } } +} +impl<'cache, 'interner, I> GreenNodeBuilder<'cache, 'interner, I> +where + I: Interner, +{ /// Reusing `NodeCache` between different `GreenNodeBuilder`s saves memory. /// It allows to structurally share underlying trees. - pub fn with_cache(cache: &mut NodeCache) -> GreenNodeBuilder<'_> { - GreenNodeBuilder { + pub fn with_cache(cache: &'cache mut NodeCache<'interner, I>) -> Self { + Self { cache: MaybeOwned::Borrowed(cache), parents: Vec::with_capacity(8), children: Vec::with_capacity(8), @@ -268,15 +295,12 @@ impl GreenNodeBuilder<'_> { /// `start_node_at` and `finish_node` calls /// are paired! #[inline] - pub fn finish(mut self) -> (GreenNode, Option>) { + pub fn finish(mut self) -> (GreenNode, Option) { assert_eq!(self.children.len(), 1); - let resolver = match self.cache { - MaybeOwned::Owned(cache) => Some(cache.interner), - MaybeOwned::Borrowed(_) => None, - }; + let resolver = self.cache.as_owned().and_then(|cache| cache.interner.as_owned()); match self.children.pop().unwrap() { NodeOrToken::Node(node) => (node, resolver), - NodeOrToken::Token(_) => panic!(), + NodeOrToken::Token(_) => panic!("called `finish` on a `GreenNodeBuilder` which only contained a token"), } } } diff --git a/src/lib.rs b/src/lib.rs index 978cc71..1259195 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ use std::fmt; pub use text_size::{TextLen, TextRange, TextSize}; pub use crate::{ - green::{Checkpoint, Children, GreenNode, GreenNodeBuilder, GreenToken, SyntaxKind}, + green::{Checkpoint, Children, GreenNode, GreenNodeBuilder, GreenToken, NodeCache, SyntaxKind}, syntax::{SyntaxElement, SyntaxElementChildren, SyntaxElementRef, SyntaxNode, SyntaxNodeChildren, SyntaxToken}, syntax_text::SyntaxText, utility_types::{Direction, NodeOrToken, TokenAtOffset, WalkEvent}, diff --git a/tests/basic.rs b/tests/basic.rs index 382092d..0265fbe 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,8 +1,8 @@ mod common; use common::TestLang; -use cstree::{GreenNodeBuilder, SyntaxKind, SyntaxNode, TextRange}; -use lasso::Resolver; +use cstree::{GreenNodeBuilder, NodeCache, SyntaxKind, SyntaxNode, TextRange}; +use lasso::{Interner, Resolver, Rodeo}; #[derive(Debug)] enum Element<'s> { @@ -26,7 +26,21 @@ fn build_tree(root: &Element<'_>) -> (SyntaxNode, impl Resolver) (SyntaxNode::new_root(node), interner.unwrap()) } -fn build_recursive(root: &Element<'_>, builder: &mut GreenNodeBuilder, mut from: u16) -> u16 { +fn build_tree_with_cache<'c, 'i, D, I>(root: &Element<'_>, cache: &'c mut NodeCache<'i, I>) -> SyntaxNode +where + I: Interner, +{ + let mut builder = GreenNodeBuilder::with_cache(cache); + build_recursive(root, &mut builder, 0); + let (node, interner) = builder.finish(); + assert!(interner.is_none()); + SyntaxNode::new_root(node) +} + +fn build_recursive<'c, 'i, I>(root: &Element<'_>, builder: &mut GreenNodeBuilder<'c, 'i, I>, mut from: u16) -> u16 +where + I: Interner, +{ match root { Element::Node(children) => { builder.start_node(SyntaxKind(from)); @@ -98,3 +112,30 @@ fn data() { assert_eq!(node2.get_data(), None); } } + +#[test] +fn with_interner() { + let mut interner = Rodeo::new(); + let mut cache = NodeCache::with_interner(&mut interner); + let tree = two_level_tree(); + let tree = build_tree_with_cache::<(), _>(&tree, &mut cache); + let resolver = interner; + + assert_eq!(tree.syntax_kind(), SyntaxKind(0)); + assert_eq!(tree.kind(), SyntaxKind(0)); + { + let leaf1_0 = tree.children().nth(1).unwrap().children_with_tokens().nth(0).unwrap(); + let leaf1_0 = leaf1_0.into_token().unwrap(); + assert_eq!(leaf1_0.syntax_kind(), SyntaxKind(5)); + assert_eq!(leaf1_0.kind(), SyntaxKind(5)); + assert_eq!(leaf1_0.text(&resolver), "1.0"); + assert_eq!(leaf1_0.text_range(), TextRange::at(6.into(), 3.into())); + } + { + let node2 = tree.children().nth(2).unwrap(); + assert_eq!(node2.syntax_kind(), SyntaxKind(6)); + assert_eq!(node2.kind(), SyntaxKind(6)); + assert_eq!(node2.children_with_tokens().count(), 3); + assert_eq!(node2.text(&resolver), "2.02.12.2"); + } +}