use std::borrow::Cow;
use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::{Delimiter, DelimiterKind, DoubleSpan, Spanned, TokenTree};
type PunctuatedNodes<N> = (N, Vec<(N, N)>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CSTNodeClassLabel<'syntax> {
pub name: Cow<'syntax, str>,
pub named: bool,
}
impl<'syntax> CSTNodeClassLabel<'syntax> {
pub fn new(name: impl Into<Cow<'syntax, str>>, named: bool) -> Self {
Self {
name: name.into(),
named,
}
}
pub fn matches(&self, name: &str, named: bool) -> bool {
self.named == named && self.name == name
}
}
pub struct ExternalCstChild<N> {
pub field: Option<&'static str>,
pub node: N,
}
pub trait ExternalCstNode: Sized + Spanned {
type Children<'syntax>: Iterator<Item = ExternalCstChild<Self>>
where
Self: 'syntax;
fn identity(&self) -> CSTNodeClassLabel<'_>;
fn children(&self) -> Self::Children<'_>;
fn text(&self) -> Cow<'_, str>;
fn is_extra(&self) -> bool {
false
}
}
pub trait Token: Spanned + Sized {
const SPELLING: &'static str;
fn matches_token_tree(token: &TokenTree) -> bool;
fn from_token_tree(token: TokenTree) -> Option<Self>;
}
pub trait Reconstruct<N: ExternalCstNode>: Sized {
fn matches(node: &N) -> bool;
fn reconstruct(node: N) -> Result<Self, ReconstructError>;
}
pub struct ChildCursor<N> {
children: Vec<Option<ExternalCstChild<N>>>,
parent: String,
}
impl<N: ExternalCstNode> ChildCursor<N> {
pub fn new(parent: &N) -> Self {
Self {
children: parent.children().map(Some).collect(),
parent: parent.identity().name.into_owned(),
}
}
pub fn required_field(&mut self, field: &'static str) -> Result<N, ReconstructError> {
self.optional_field(field)
.ok_or_else(|| ReconstructError::MissingField {
node: self.parent.clone(),
field,
})
}
pub fn optional_field(&mut self, field: &'static str) -> Option<N> {
self.take_first(|child| !child.node.is_extra() && child.field == Some(field))
}
pub fn repeated_field(&mut self, field: &'static str) -> Vec<N> {
self.take_all(|child| !child.node.is_extra() && child.field == Some(field))
}
pub fn required_matching<T>(&mut self) -> Result<N, ReconstructError>
where
T: Reconstruct<N>,
{
self.optional_matching::<T>()
.ok_or_else(|| ReconstructError::MissingChild {
node: self.parent.clone(),
expected: std::any::type_name::<T>(),
})
}
pub fn optional_matching<T>(&mut self) -> Option<N>
where
T: Reconstruct<N>,
{
self.take_first(|child| {
!child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
})
}
pub fn repeated_matching<T>(&mut self) -> Vec<N>
where
T: Reconstruct<N>,
{
self.take_all(|child| {
!child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
})
}
pub fn required_anonymous(&mut self, spelling: &'static str) -> Result<N, ReconstructError> {
self.take_first(|child| {
child.field.is_none() && child.node.identity().matches(spelling, false)
})
.ok_or_else(|| ReconstructError::MissingChild {
node: self.parent.clone(),
expected: spelling,
})
}
pub fn optional_anonymous(&mut self, spelling: &'static str) -> Option<N> {
self.take_first(|child| {
child.field.is_none() && child.node.identity().matches(spelling, false)
})
}
pub fn required_delimiter(&mut self) -> Result<Delimiter, ReconstructError> {
for kind in [
DelimiterKind::Parenthesis,
DelimiterKind::Bracket,
DelimiterKind::Brace,
DelimiterKind::AngleBar,
] {
let Some(opening) = self.optional_anonymous(kind.opening()) else {
continue;
};
let closing = self.required_anonymous(kind.closing())?;
return Ok(Delimiter::new(
kind,
DoubleSpan::new(opening.span(), closing.span()),
));
}
Err(ReconstructError::MissingChild {
node: self.parent.clone(),
expected: "a delimiter",
})
}
pub fn punctuated(
&mut self,
values: Vec<N>,
spelling: &'static str,
) -> Result<PunctuatedNodes<N>, ReconstructError> {
let punctuation = self.take_all(|child| {
child.field.is_none() && child.node.identity().matches(spelling, false)
});
let value_count = values.len();
let punctuation_count = punctuation.len();
let mut values = values.into_iter();
let Some(first) = values.next() else {
return Err(ReconstructError::InvalidPunctuated {
node: self.parent.clone(),
values: value_count,
punctuation: punctuation_count,
});
};
if punctuation_count + 1 != value_count {
return Err(ReconstructError::InvalidPunctuated {
node: self.parent.clone(),
values: value_count,
punctuation: punctuation_count,
});
}
Ok((first, punctuation.into_iter().zip(values).collect()))
}
pub fn optional_punctuated(
&mut self,
values: Vec<N>,
spelling: &'static str,
) -> Result<Option<PunctuatedNodes<N>>, ReconstructError> {
if values.is_empty() {
let punctuation = self.take_all(|child| {
child.field.is_none() && child.node.identity().matches(spelling, false)
});
return if punctuation.is_empty() {
Ok(None)
} else {
Err(ReconstructError::InvalidPunctuated {
node: self.parent.clone(),
values: 0,
punctuation: punctuation.len(),
})
};
}
self.punctuated(values, spelling).map(Some)
}
fn take_first(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Option<N> {
self.children
.iter_mut()
.find_map(|child| child.take_if(|child| predicate(child)))
.map(|child| child.node)
}
fn take_all(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Vec<N> {
self.children
.iter_mut()
.filter_map(|child| {
child
.take_if(|child| predicate(child))
.map(|child| child.node)
})
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReconstructError {
WrongNode {
expected: &'static str,
expected_named: bool,
actual: String,
actual_named: bool,
},
WrongCategory {
category: &'static str,
actual: String,
actual_named: bool,
},
MissingField {
node: String,
field: &'static str,
},
MissingChild {
node: String,
expected: &'static str,
},
InvalidPunctuated {
node: String,
values: usize,
punctuation: usize,
},
}
impl ReconstructError {
pub fn wrong_node(
expected: &'static str,
expected_named: bool,
actual: CSTNodeClassLabel<'_>,
) -> Self {
Self::WrongNode {
expected,
expected_named,
actual: actual.name.into_owned(),
actual_named: actual.named,
}
}
pub fn wrong_category(category: &'static str, actual: CSTNodeClassLabel<'_>) -> Self {
Self::WrongCategory {
category,
actual: actual.name.into_owned(),
actual_named: actual.named,
}
}
}
impl Display for ReconstructError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::WrongNode {
expected,
expected_named,
actual,
actual_named,
} => write!(
formatter,
"expected {} node {expected:?}, found {} node {actual:?}",
visibility(*expected_named),
visibility(*actual_named),
),
Self::WrongCategory {
category,
actual,
actual_named,
} => write!(
formatter,
"node {} {actual:?} does not belong to {category}",
visibility(*actual_named),
),
Self::MissingField { node, field } => {
write!(formatter, "node {node:?} has no {field:?} field")
}
Self::MissingChild { node, expected } => {
write!(formatter, "node {node:?} has no child matching {expected}")
}
Self::InvalidPunctuated {
node,
values,
punctuation,
} => write!(
formatter,
"node {node:?} has {values} punctuated values but {punctuation} separators",
),
}
}
}
impl Error for ReconstructError {}
fn visibility(named: bool) -> &'static str {
if named { "named" } else { "anonymous" }
}