From 3ef1e7e82b0d43237217cff06a9bc343751cbde9 Mon Sep 17 00:00:00 2001 From: Domenic Quirl Date: Sat, 28 Aug 2021 12:02:50 +0200 Subject: [PATCH] add `write_` interface for syntax nodes and tokens and refactor `Debug` and `Display` to use it --- src/syntax/element.rs | 98 +++++++++++++++++++++++++++++++++++++++--- src/syntax/node.rs | 62 +++++++++++++++++--------- src/syntax/resolved.rs | 17 +++++--- src/syntax/token.rs | 59 ++++++++++++++++++------- 4 files changed, 188 insertions(+), 48 deletions(-) diff --git a/src/syntax/element.rs b/src/syntax/element.rs index 682de17..69fd80a 100644 --- a/src/syntax/element.rs +++ b/src/syntax/element.rs @@ -1,4 +1,4 @@ -use std::sync::atomic::AtomicU32; +use std::{fmt, sync::atomic::AtomicU32}; use lasso::Resolver; use text_size::{TextRange, TextSize}; @@ -22,13 +22,57 @@ impl From> for SyntaxElement { } impl SyntaxElement { - #[allow(missing_docs)] - pub fn display(&self, resolver: &impl Resolver) -> String { + /// Returns this element's [`Display`](fmt::Display) representation as a string. + /// + /// To avoid allocating for every element, see [`write_display`](type.SyntaxElement.html#method.write_display). + pub fn display(&self, resolver: &R) -> String + where + R: Resolver + ?Sized, + { match self { NodeOrToken::Node(it) => it.display(resolver), NodeOrToken::Token(it) => it.display(resolver), } } + + /// Writes this element's [`Display`](fmt::Display) representation into the given `target`. + pub fn write_display(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result + where + R: Resolver + ?Sized, + { + match self { + NodeOrToken::Node(it) => it.write_display(resolver, target), + NodeOrToken::Token(it) => it.write_display(resolver, target), + } + } + + /// Returns this element's [`Debug`](fmt::Debug) representation as a string. + /// If `recursive` is `true`, prints the entire subtree rooted in this element. + /// Otherwise, only this element's kind and range are written. + /// + /// To avoid allocating for every element, see [`write_debug`](type.SyntaxElement.html#method.write_debug). + pub fn debug(&self, resolver: &R, recursive: bool) -> String + where + R: Resolver + ?Sized, + { + match self { + NodeOrToken::Node(it) => it.debug(resolver, recursive), + NodeOrToken::Token(it) => it.debug(resolver), + } + } + + /// Writes this element's [`Debug`](fmt::Debug) representation into the given `target`. + /// If `recursive` is `true`, prints the entire subtree rooted in this element. + /// Otherwise, only this element's kind and range are written. + pub fn write_debug(&self, resolver: &R, target: &mut impl fmt::Write, recursive: bool) -> fmt::Result + where + R: Resolver + ?Sized, + { + match self { + NodeOrToken::Node(it) => it.write_debug(resolver, target, recursive), + NodeOrToken::Token(it) => it.write_debug(resolver, target), + } + } } /// A reference to an element of the tree, can be either a reference to a node or one to a token. @@ -56,13 +100,57 @@ impl<'a, L: Language, D> From<&'a SyntaxElement> for SyntaxElementRef<'a, } impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { - #[allow(missing_docs)] - pub fn display(&self, resolver: &impl Resolver) -> String { + /// Returns this element's [`Display`](fmt::Display) representation as a string. + /// + /// To avoid allocating for every element, see [`write_display`](type.SyntaxElementRef.html#method.write_display). + pub fn display(&self, resolver: &R) -> String + where + R: Resolver + ?Sized, + { match self { NodeOrToken::Node(it) => it.display(resolver), NodeOrToken::Token(it) => it.display(resolver), } } + + /// Writes this element's [`Display`](fmt::Display) representation into the given `target`. + pub fn write_display(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result + where + R: Resolver + ?Sized, + { + match self { + NodeOrToken::Node(it) => it.write_display(resolver, target), + NodeOrToken::Token(it) => it.write_display(resolver, target), + } + } + + /// Returns this element's [`Debug`](fmt::Debug) representation as a string. + /// If `recursive` is `true`, prints the entire subtree rooted in this element. + /// Otherwise, only this element's kind and range are written. + /// + /// To avoid allocating for every element, see [`write_debug`](type.SyntaxElementRef.html#method.write_debug). + pub fn debug(&self, resolver: &R, recursive: bool) -> String + where + R: Resolver + ?Sized, + { + match self { + NodeOrToken::Node(it) => it.debug(resolver, recursive), + NodeOrToken::Token(it) => it.debug(resolver), + } + } + + /// Writes this element's [`Debug`](fmt::Debug) representation into the given `target`. + /// If `recursive` is `true`, prints the entire subtree rooted in this element. + /// Otherwise, only this element's kind and range are written. + pub fn write_debug(&self, resolver: &R, target: &mut impl fmt::Write, recursive: bool) -> fmt::Result + where + R: Resolver + ?Sized, + { + match self { + NodeOrToken::Node(it) => it.write_debug(resolver, target, recursive), + NodeOrToken::Token(it) => it.write_debug(resolver, target), + } + } } impl SyntaxElement { diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 55691d3..922c65f 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -9,7 +9,7 @@ use crate::{ use parking_lot::RwLock; use std::{ cell::UnsafeCell, - fmt::Write, + fmt, hash::{Hash, Hasher}, iter, ptr, sync::{ @@ -33,55 +33,75 @@ unsafe impl Send for SyntaxNode {} unsafe impl Sync for SyntaxNode {} impl SyntaxNode { - #[allow(missing_docs)] - pub fn debug(&self, resolver: &R, recursive: bool) -> String + /// Writes this node's [`Debug`](fmt::Debug) representation into the given `target`. + /// If `recursive` is `true`, prints the entire subtree rooted in this node. + /// Otherwise, only this node's kind and range are written. + pub fn write_debug(&self, resolver: &R, target: &mut impl fmt::Write, recursive: bool) -> fmt::Result where R: Resolver + ?Sized, { - // NOTE: `fmt::Write` methods on `String` never fail - let mut res = String::new(); if recursive { let mut level = 0; for event in self.preorder_with_tokens() { match event { WalkEvent::Enter(element) => { for _ in 0..level { - write!(res, " ").unwrap(); + write!(target, " ")?; } - writeln!( - res, - "{}", - match element { - NodeOrToken::Node(node) => node.debug(resolver, false), - NodeOrToken::Token(token) => token.debug(resolver), - }, - ) - .unwrap(); + element.write_debug(resolver, target, false)?; + writeln!(target)?; level += 1; } WalkEvent::Leave(_) => level -= 1, } } assert_eq!(level, 0); + Ok(()) } else { - write!(res, "{:?}@{:?}", self.kind(), self.text_range()).unwrap(); + write!(target, "{:?}@{:?}", self.kind(), self.text_range()) } - res } - #[allow(missing_docs)] - pub fn display(&self, resolver: &R) -> String + /// Returns this node's [`Debug`](fmt::Debug) representation as a string. + /// If `recursive` is `true`, prints the entire subtree rooted in this node. + /// Otherwise, only this node's kind and range are written. + /// + /// To avoid allocating for every node, see [`write_debug`](SyntaxNode::write_debug). + #[inline] + pub fn debug(&self, resolver: &R, recursive: bool) -> String where R: Resolver + ?Sized, { + // NOTE: `fmt::Write` methods on `String` never fail let mut res = String::new(); + self.write_debug(resolver, &mut res, recursive).unwrap(); + res + } + + /// Writes this node's [`Display`](fmt::Display) representation into the given `target`. + pub fn write_display(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result + where + R: Resolver + ?Sized, + { self.preorder_with_tokens() .filter_map(|event| match event { WalkEvent::Enter(NodeOrToken::Token(token)) => Some(token), _ => None, }) - .try_for_each(|it| write!(res, "{}", it.display(resolver))) - .unwrap(); + .try_for_each(|it| it.write_display(resolver, target)) + } + + /// Returns this node's [`Display`](fmt::Display) representation as a string. + /// + /// To avoid allocating for every node, see [`write_display`](SyntaxNode::write_display). + #[inline] + pub fn display(&self, resolver: &R) -> String + where + R: Resolver + ?Sized, + { + // NOTE: `fmt::Write` methods on `String` never fail + let mut res = String::new(); + self.write_display(resolver, &mut res).unwrap(); res } diff --git a/src/syntax/resolved.rs b/src/syntax/resolved.rs index 35e7497..67c6841 100644 --- a/src/syntax/resolved.rs +++ b/src/syntax/resolved.rs @@ -184,13 +184,13 @@ impl ResolvedNode { impl fmt::Debug for ResolvedNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.debug(&**self.resolver(), f.alternate())) + self.write_debug(&**self.resolver(), f, f.alternate()) } } impl fmt::Display for ResolvedNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.display(&**self.resolver())) + self.write_display(&**self.resolver(), f) } } @@ -198,19 +198,19 @@ impl ResolvedToken { /// Uses the resolver associated with this tree to return the source text of this token. #[inline] pub fn text(&self) -> &str { - self.green().text(&**self.parent().resolver()) + self.green().text(&**self.resolver()) } } impl fmt::Debug for ResolvedToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.debug(&**self.parent().resolver())) + self.write_debug(&**self.resolver(), f) } } impl fmt::Display for ResolvedToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.display(&**self.parent().resolver())) + self.write_display(&**self.resolver(), f) } } @@ -262,7 +262,7 @@ macro_rules! forward_node { } impl ResolvedNode { - /// If there is a resolver associated with this tree, returns it. + /// Returns the [`Resolver`] associated with this tree. pub fn resolver(&self) -> &StdArc { self.syntax.resolver().unwrap() } @@ -494,6 +494,11 @@ impl ResolvedNode { } impl ResolvedToken { + /// Returns the [`Resolver`] associated with this tree. + pub fn resolver(&self) -> &StdArc { + self.syntax.resolver().unwrap() + } + /// Always returns `Some(self)`. /// /// This method mostly exists to allow the convenience of being agnostic over [`SyntaxToken`] vs [`ResolvedToken`]. diff --git a/src/syntax/token.rs b/src/syntax/token.rs index c0f3075..fb68fe4 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -1,7 +1,8 @@ use std::{ - fmt::Write, + fmt, hash::{Hash, Hasher}, iter, + sync::Arc as StdArc, }; use lasso::Resolver; @@ -45,29 +46,51 @@ impl PartialEq for SyntaxToken { impl Eq for SyntaxToken {} impl SyntaxToken { - #[allow(missing_docs)] - pub fn debug(&self, resolver: &R) -> String + /// Writes this token's [`Debug`](fmt::Debug) representation into the given `target`. + pub fn write_debug(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result where R: Resolver + ?Sized, { - let mut res = String::new(); - write!(res, "{:?}@{:?}", self.kind(), self.text_range()).unwrap(); - if self.resolve_text(resolver).len() < 25 { - write!(res, " {:?}", self.resolve_text(resolver)).unwrap(); - return res; - } + write!(target, "{:?}@{:?}", self.kind(), self.text_range())?; let text = self.resolve_text(resolver); + if text.len() < 25 { + return write!(target, " {:?}", text); + } + for idx in 21..25 { if text.is_char_boundary(idx) { - let text = format!("{} ...", &text[..idx]); - write!(res, " {:?}", text).unwrap(); - return res; + return write!(target, r#" "{} ...""#, &text[..idx]); } } unreachable!() } - #[allow(missing_docs)] + /// Returns this token's [`Debug`](fmt::Debug) representation as a string. + /// + /// To avoid allocating for every token, see [`write_debug`](SyntaxToken::write_debug). + pub fn debug(&self, resolver: &R) -> String + where + R: Resolver + ?Sized, + { + // NOTE: `fmt::Write` methods on `String` never fail + let mut res = String::new(); + self.write_debug(resolver, &mut res).unwrap(); + res + } + + /// Writes this token's [`Display`](fmt::Display) representation into the given `target`. + #[inline] + pub fn write_display(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result + where + R: Resolver + ?Sized, + { + write!(target, "{}", self.resolve_text(resolver)) + } + + /// Returns this token's [`Display`](fmt::Display) representation as a string. + /// + /// To avoid allocating for every token, see [`write_display`](SyntaxToken::write_display). + #[inline] pub fn display(&self, resolver: &R) -> String where R: Resolver + ?Sized, @@ -75,13 +98,17 @@ impl SyntaxToken { self.resolve_text(resolver).to_string() } + /// If there is a resolver associated with this tree, returns it. + #[inline] + pub fn resolver(&self) -> Option<&StdArc> { + self.parent.resolver() + } + /// Turns this token into a [`ResolvedToken`], but only if there is a resolver associated with this tree. #[inline] pub fn try_resolved(&self) -> Option<&ResolvedToken> { // safety: we only coerce if `resolver` exists - self.parent() - .resolver() - .map(|_| unsafe { ResolvedToken::coerce_ref(self) }) + self.resolver().map(|_| unsafe { ResolvedToken::coerce_ref(self) }) } /// Turns this token into a [`ResolvedToken`].