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:
parent
cc5ea59d4b
commit
3ef1e7e82b
4 changed files with 188 additions and 48 deletions
|
@ -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> {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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`].
|
||||||
|
|
|
@ -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`].
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue