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

add write_ interface for syntax nodes and tokens

and refactor `Debug` and `Display` to use it
This commit is contained in:
Domenic Quirl 2021-08-28 12:02:50 +02:00
parent cc5ea59d4b
commit 3ef1e7e82b
4 changed files with 188 additions and 48 deletions

View file

@ -1,4 +1,4 @@
use std::sync::atomic::AtomicU32; use std::{fmt, sync::atomic::AtomicU32};
use lasso::Resolver; use lasso::Resolver;
use text_size::{TextRange, TextSize}; use text_size::{TextRange, TextSize};
@ -22,13 +22,57 @@ impl<L: Language, D> From<SyntaxToken<L, D>> for SyntaxElement<L, D> {
} }
impl<L: Language, D> SyntaxElement<L, D> { impl<L: Language, D> SyntaxElement<L, D> {
#[allow(missing_docs)] /// Returns this element's [`Display`](fmt::Display) representation as a string.
pub fn display(&self, resolver: &impl Resolver) -> String { ///
/// To avoid allocating for every element, see [`write_display`](type.SyntaxElement.html#method.write_display).
pub fn display<R>(&self, resolver: &R) -> String
where
R: Resolver + ?Sized,
{
match self { match self {
NodeOrToken::Node(it) => it.display(resolver), NodeOrToken::Node(it) => it.display(resolver),
NodeOrToken::Token(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<R>(&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<R>(&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<R>(&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. /// 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<L, D>> for SyntaxElementRef<'a,
} }
impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> {
#[allow(missing_docs)] /// Returns this element's [`Display`](fmt::Display) representation as a string.
pub fn display(&self, resolver: &impl Resolver) -> String { ///
/// To avoid allocating for every element, see [`write_display`](type.SyntaxElementRef.html#method.write_display).
pub fn display<R>(&self, resolver: &R) -> String
where
R: Resolver + ?Sized,
{
match self { match self {
NodeOrToken::Node(it) => it.display(resolver), NodeOrToken::Node(it) => it.display(resolver),
NodeOrToken::Token(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<R>(&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<R>(&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<R>(&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<L: Language, D> SyntaxElement<L, D> { impl<L: Language, D> SyntaxElement<L, D> {

View file

@ -9,7 +9,7 @@ use crate::{
use parking_lot::RwLock; use parking_lot::RwLock;
use std::{ use std::{
cell::UnsafeCell, cell::UnsafeCell,
fmt::Write, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter, ptr, iter, ptr,
sync::{ sync::{
@ -33,55 +33,75 @@ unsafe impl<L: Language, D: 'static> Send for SyntaxNode<L, D> {}
unsafe impl<L: Language, D: 'static> Sync for SyntaxNode<L, D> {} unsafe impl<L: Language, D: 'static> Sync for SyntaxNode<L, D> {}
impl<L: Language, D> SyntaxNode<L, D> { impl<L: Language, D> SyntaxNode<L, D> {
#[allow(missing_docs)] /// Writes this node's [`Debug`](fmt::Debug) representation into the given `target`.
pub fn debug<R>(&self, resolver: &R, recursive: bool) -> String /// 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<R>(&self, resolver: &R, target: &mut impl fmt::Write, recursive: bool) -> fmt::Result
where where
R: Resolver + ?Sized, R: Resolver + ?Sized,
{ {
// NOTE: `fmt::Write` methods on `String` never fail
let mut res = String::new();
if recursive { if recursive {
let mut level = 0; let mut level = 0;
for event in self.preorder_with_tokens() { for event in self.preorder_with_tokens() {
match event { match event {
WalkEvent::Enter(element) => { WalkEvent::Enter(element) => {
for _ in 0..level { for _ in 0..level {
write!(res, " ").unwrap(); write!(target, " ")?;
} }
writeln!( element.write_debug(resolver, target, false)?;
res, writeln!(target)?;
"{}",
match element {
NodeOrToken::Node(node) => node.debug(resolver, false),
NodeOrToken::Token(token) => token.debug(resolver),
},
)
.unwrap();
level += 1; level += 1;
} }
WalkEvent::Leave(_) => level -= 1, WalkEvent::Leave(_) => level -= 1,
} }
} }
assert_eq!(level, 0); assert_eq!(level, 0);
Ok(())
} else { } else {
write!(res, "{:?}@{:?}", self.kind(), self.text_range()).unwrap(); write!(target, "{:?}@{:?}", self.kind(), self.text_range())
} }
res
} }
#[allow(missing_docs)] /// Returns this node's [`Debug`](fmt::Debug) representation as a string.
pub fn display<R>(&self, resolver: &R) -> 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<R>(&self, resolver: &R, recursive: bool) -> String
where where
R: Resolver + ?Sized, R: Resolver + ?Sized,
{ {
// NOTE: `fmt::Write` methods on `String` never fail
let mut res = String::new(); 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<R>(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result
where
R: Resolver + ?Sized,
{
self.preorder_with_tokens() self.preorder_with_tokens()
.filter_map(|event| match event { .filter_map(|event| match event {
WalkEvent::Enter(NodeOrToken::Token(token)) => Some(token), WalkEvent::Enter(NodeOrToken::Token(token)) => Some(token),
_ => None, _ => None,
}) })
.try_for_each(|it| write!(res, "{}", it.display(resolver))) .try_for_each(|it| it.write_display(resolver, target))
.unwrap(); }
/// 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<R>(&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 res
} }

View file

@ -184,13 +184,13 @@ impl<L: Language, D> ResolvedNode<L, D> {
impl<L: Language, D> fmt::Debug for ResolvedNode<L, D> { impl<L: Language, D> fmt::Debug for ResolvedNode<L, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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<L: Language, D> fmt::Display for ResolvedNode<L, D> { impl<L: Language, D> fmt::Display for ResolvedNode<L, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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<L: Language, D> ResolvedToken<L, D> {
/// Uses the resolver associated with this tree to return the source text of this token. /// Uses the resolver associated with this tree to return the source text of this token.
#[inline] #[inline]
pub fn text(&self) -> &str { pub fn text(&self) -> &str {
self.green().text(&**self.parent().resolver()) self.green().text(&**self.resolver())
} }
} }
impl<L: Language, D> fmt::Debug for ResolvedToken<L, D> { impl<L: Language, D> fmt::Debug for ResolvedToken<L, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.debug(&**self.parent().resolver())) self.write_debug(&**self.resolver(), f)
} }
} }
impl<L: Language, D> fmt::Display for ResolvedToken<L, D> { impl<L: Language, D> fmt::Display for ResolvedToken<L, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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<L: Language, D> ResolvedNode<L, D> { impl<L: Language, D> ResolvedNode<L, D> {
/// If there is a resolver associated with this tree, returns it. /// Returns the [`Resolver`] associated with this tree.
pub fn resolver(&self) -> &StdArc<dyn Resolver> { pub fn resolver(&self) -> &StdArc<dyn Resolver> {
self.syntax.resolver().unwrap() self.syntax.resolver().unwrap()
} }
@ -494,6 +494,11 @@ impl<L: Language, D> ResolvedNode<L, D> {
} }
impl<L: Language, D> ResolvedToken<L, D> { impl<L: Language, D> ResolvedToken<L, D> {
/// Returns the [`Resolver`] associated with this tree.
pub fn resolver(&self) -> &StdArc<dyn Resolver> {
self.syntax.resolver().unwrap()
}
/// Always returns `Some(self)`. /// Always returns `Some(self)`.
/// ///
/// This method mostly exists to allow the convenience of being agnostic over [`SyntaxToken`] vs [`ResolvedToken`]. /// This method mostly exists to allow the convenience of being agnostic over [`SyntaxToken`] vs [`ResolvedToken`].

View file

@ -1,7 +1,8 @@
use std::{ use std::{
fmt::Write, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter, iter,
sync::Arc as StdArc,
}; };
use lasso::Resolver; use lasso::Resolver;
@ -45,29 +46,51 @@ impl<L: Language, D> PartialEq for SyntaxToken<L, D> {
impl<L: Language, D> Eq for SyntaxToken<L, D> {} impl<L: Language, D> Eq for SyntaxToken<L, D> {}
impl<L: Language, D> SyntaxToken<L, D> { impl<L: Language, D> SyntaxToken<L, D> {
#[allow(missing_docs)] /// Writes this token's [`Debug`](fmt::Debug) representation into the given `target`.
pub fn debug<R>(&self, resolver: &R) -> String pub fn write_debug<R>(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result
where where
R: Resolver + ?Sized, R: Resolver + ?Sized,
{ {
let mut res = String::new(); write!(target, "{:?}@{:?}", self.kind(), self.text_range())?;
write!(res, "{:?}@{:?}", self.kind(), self.text_range()).unwrap();
if self.resolve_text(resolver).len() < 25 {
write!(res, " {:?}", self.resolve_text(resolver)).unwrap();
return res;
}
let text = self.resolve_text(resolver); let text = self.resolve_text(resolver);
if text.len() < 25 {
return write!(target, " {:?}", text);
}
for idx in 21..25 { for idx in 21..25 {
if text.is_char_boundary(idx) { if text.is_char_boundary(idx) {
let text = format!("{} ...", &text[..idx]); return write!(target, r#" "{} ...""#, &text[..idx]);
write!(res, " {:?}", text).unwrap();
return res;
} }
} }
unreachable!() 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<R>(&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<R>(&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<R>(&self, resolver: &R) -> String pub fn display<R>(&self, resolver: &R) -> String
where where
R: Resolver + ?Sized, R: Resolver + ?Sized,
@ -75,13 +98,17 @@ impl<L: Language, D> SyntaxToken<L, D> {
self.resolve_text(resolver).to_string() self.resolve_text(resolver).to_string()
} }
/// If there is a resolver associated with this tree, returns it.
#[inline]
pub fn resolver(&self) -> Option<&StdArc<dyn Resolver>> {
self.parent.resolver()
}
/// Turns this token into a [`ResolvedToken`], but only if there is a resolver associated with this tree. /// Turns this token into a [`ResolvedToken`], but only if there is a resolver associated with this tree.
#[inline] #[inline]
pub fn try_resolved(&self) -> Option<&ResolvedToken<L, D>> { pub fn try_resolved(&self) -> Option<&ResolvedToken<L, D>> {
// safety: we only coerce if `resolver` exists // safety: we only coerce if `resolver` exists
self.parent() self.resolver().map(|_| unsafe { ResolvedToken::coerce_ref(self) })
.resolver()
.map(|_| unsafe { ResolvedToken::coerce_ref(self) })
} }
/// Turns this token into a [`ResolvedToken`]. /// Turns this token into a [`ResolvedToken`].