1use std::borrow::Cow;
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13
14use crate::{Spanned, TokenTree};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct CSTNodeClassLabel<'syntax> {
19 pub name: Cow<'syntax, str>,
20 pub named: bool,
21}
22
23impl<'syntax> CSTNodeClassLabel<'syntax> {
24 pub fn new(name: impl Into<Cow<'syntax, str>>, named: bool) -> Self {
25 Self {
26 name: name.into(),
27 named,
28 }
29 }
30
31 pub fn matches(&self, name: &str, named: bool) -> bool {
32 self.named == named && self.name == name
33 }
34}
35
36pub struct ExternalCstChild<N> {
37 pub field: Option<&'static str>,
38 pub node: N,
39}
40
41pub trait ExternalCstNode: Sized + Spanned {
46 type Children<'syntax>: Iterator<Item = ExternalCstChild<Self>>
47 where
48 Self: 'syntax;
49
50 fn identity(&self) -> CSTNodeClassLabel<'_>;
51 fn children(&self) -> Self::Children<'_>;
52 fn text(&self) -> Cow<'_, str>;
53 fn is_extra(&self) -> bool {
54 false
55 }
56}
57
58pub trait Token: Spanned + Sized {
64 const SPELLING: &'static str;
66
67 fn matches_token_tree(token: &TokenTree) -> bool;
69
70 fn from_token_tree(token: TokenTree) -> Option<Self>;
72}
73
74pub trait Reconstruct<N: ExternalCstNode>: Sized {
76 fn matches(node: &N) -> bool;
77 fn reconstruct(node: N) -> Result<Self, ReconstructError>;
78}
79
80pub struct ChildCursor<N> {
82 children: Vec<Option<ExternalCstChild<N>>>,
83 parent: String,
84}
85
86impl<N: ExternalCstNode> ChildCursor<N> {
87 pub fn new(parent: &N) -> Self {
88 Self {
89 children: parent
90 .children()
91 .filter(|child| !child.node.is_extra())
92 .map(Some)
93 .collect(),
94 parent: parent.identity().name.into_owned(),
95 }
96 }
97
98 pub fn required_field(&mut self, field: &'static str) -> Result<N, ReconstructError> {
99 self.optional_field(field)
100 .ok_or_else(|| ReconstructError::MissingField {
101 node: self.parent.clone(),
102 field,
103 })
104 }
105
106 pub fn optional_field(&mut self, field: &'static str) -> Option<N> {
107 self.take_first(|child| child.field == Some(field))
108 }
109
110 pub fn repeated_field(&mut self, field: &'static str) -> Vec<N> {
111 self.take_all(|child| child.field == Some(field))
112 }
113
114 pub fn required_matching<T>(&mut self) -> Result<N, ReconstructError>
115 where
116 T: Reconstruct<N>,
117 {
118 self.optional_matching::<T>()
119 .ok_or_else(|| ReconstructError::MissingChild {
120 node: self.parent.clone(),
121 expected: std::any::type_name::<T>(),
122 })
123 }
124
125 pub fn optional_matching<T>(&mut self) -> Option<N>
126 where
127 T: Reconstruct<N>,
128 {
129 self.take_first(|child| child.field.is_none() && T::matches(&child.node))
130 }
131
132 pub fn repeated_matching<T>(&mut self) -> Vec<N>
133 where
134 T: Reconstruct<N>,
135 {
136 self.take_all(|child| child.field.is_none() && T::matches(&child.node))
137 }
138
139 fn take_first(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Option<N> {
140 self.children
141 .iter_mut()
142 .find(|child| child.as_ref().is_some_and(&predicate))
143 .and_then(Option::take)
144 .map(|child| child.node)
145 }
146
147 fn take_all(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Vec<N> {
148 self.children
149 .iter_mut()
150 .filter_map(|child| {
151 child
152 .as_ref()
153 .is_some_and(&predicate)
154 .then(|| child.take().expect("matched child").node)
155 })
156 .collect()
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub enum ReconstructError {
162 WrongNode {
163 expected: &'static str,
164 expected_named: bool,
165 actual: String,
166 actual_named: bool,
167 },
168 WrongCategory {
169 category: &'static str,
170 actual: String,
171 actual_named: bool,
172 },
173 MissingField {
174 node: String,
175 field: &'static str,
176 },
177 MissingChild {
178 node: String,
179 expected: &'static str,
180 },
181}
182
183impl ReconstructError {
184 pub fn wrong_node(
185 expected: &'static str,
186 expected_named: bool,
187 actual: CSTNodeClassLabel<'_>,
188 ) -> Self {
189 Self::WrongNode {
190 expected,
191 expected_named,
192 actual: actual.name.into_owned(),
193 actual_named: actual.named,
194 }
195 }
196
197 pub fn wrong_category(category: &'static str, actual: CSTNodeClassLabel<'_>) -> Self {
198 Self::WrongCategory {
199 category,
200 actual: actual.name.into_owned(),
201 actual_named: actual.named,
202 }
203 }
204}
205
206impl Display for ReconstructError {
207 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
208 match self {
209 Self::WrongNode {
210 expected,
211 expected_named,
212 actual,
213 actual_named,
214 } => write!(
215 formatter,
216 "expected {} node {expected:?}, found {} node {actual:?}",
217 visibility(*expected_named),
218 visibility(*actual_named),
219 ),
220 Self::WrongCategory {
221 category,
222 actual,
223 actual_named,
224 } => write!(
225 formatter,
226 "node {} {actual:?} does not belong to {category}",
227 visibility(*actual_named),
228 ),
229 Self::MissingField { node, field } => {
230 write!(formatter, "node {node:?} has no {field:?} field")
231 }
232 Self::MissingChild { node, expected } => {
233 write!(formatter, "node {node:?} has no child matching {expected}")
234 }
235 }
236 }
237}
238
239impl Error for ReconstructError {}
240
241fn visibility(named: bool) -> &'static str {
242 if named { "named" } else { "anonymous" }
243}