1
Fork 0
mirror of https://github.com/RGBCube/cstree synced 2025-07-27 09:07:44 +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 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> {
#[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<R>(&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<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.
@ -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> {
#[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<R>(&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<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> {

View file

@ -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<L: Language, D: 'static> Send for SyntaxNode<L, D> {}
unsafe impl<L: Language, D: 'static> Sync for SyntaxNode<L, D> {}
impl<L: Language, D> SyntaxNode<L, D> {
#[allow(missing_docs)]
pub fn debug<R>(&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<R>(&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<R>(&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<R>(&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<R>(&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<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
}

View file

@ -184,13 +184,13 @@ impl<L: Language, D> ResolvedNode<L, D> {
impl<L: Language, D> fmt::Debug for ResolvedNode<L, D> {
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> {
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.
#[inline]
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> {
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> {
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> {
/// 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> {
self.syntax.resolver().unwrap()
}
@ -494,6 +494,11 @@ impl<L: Language, D> ResolvedNode<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)`.
///
/// This method mostly exists to allow the convenience of being agnostic over [`SyntaxToken`] vs [`ResolvedToken`].

View file

@ -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<L: Language, D> PartialEq for SyntaxToken<L, D> {
impl<L: Language, D> Eq for SyntaxToken<L, D> {}
impl<L: Language, D> SyntaxToken<L, D> {
#[allow(missing_docs)]
pub fn debug<R>(&self, resolver: &R) -> String
/// Writes this token's [`Debug`](fmt::Debug) representation into the given `target`.
pub fn write_debug<R>(&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<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
where
R: Resolver + ?Sized,
@ -75,13 +98,17 @@ impl<L: Language, D> SyntaxToken<L, D> {
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.
#[inline]
pub fn try_resolved(&self) -> Option<&ResolvedToken<L, D>> {
// 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`].