1use std::borrow::Cow;
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13
14use crate::{Delimiter, DelimiterKind, DoubleSpan, Spanned, TokenTree};
15
16type PunctuatedNodes<N> = (N, Vec<(N, N)>);
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct CSTNodeClassLabel<'syntax> {
21 pub name: Cow<'syntax, str>,
23 pub named: bool,
25}
26
27impl<'syntax> CSTNodeClassLabel<'syntax> {
28 pub fn new(name: impl Into<Cow<'syntax, str>>, named: bool) -> Self {
30 Self {
31 name: name.into(),
32 named,
33 }
34 }
35
36 pub fn matches(&self, name: &str, named: bool) -> bool {
38 self.named == named && self.name == name
39 }
40}
41
42pub struct ExternalCstChild<N> {
44 pub field: Option<&'static str>,
46 pub node: N,
48}
49
50pub trait ExternalCstNode: Sized + Spanned {
55 type Children<'syntax>: Iterator<Item = ExternalCstChild<Self>>
57 where
58 Self: 'syntax;
59
60 fn identity(&self) -> CSTNodeClassLabel<'_>;
62 fn children(&self) -> Self::Children<'_>;
64 fn text(&self) -> Cow<'_, str>;
66 fn is_extra(&self) -> bool {
68 false
69 }
70}
71
72pub trait Token: Spanned + Sized {
77 const SPELLING: &'static str;
79
80 fn matches_token_tree(token: &TokenTree) -> bool;
82
83 fn from_token_tree(token: TokenTree) -> Option<Self>;
85}
86
87pub trait Reconstruct<N: ExternalCstNode>: Sized {
89 fn matches(node: &N) -> bool;
91 fn reconstruct(node: N) -> Result<Self, ReconstructError>;
98}
99
100pub struct ChildCursor<N> {
102 children: Vec<Option<ExternalCstChild<N>>>,
103 parent: String,
104}
105
106impl<N: ExternalCstNode> ChildCursor<N> {
107 pub fn new(parent: &N) -> Self {
109 Self {
110 children: parent.children().map(Some).collect(),
111 parent: parent.identity().name.into_owned(),
112 }
113 }
114
115 pub fn required_field(&mut self, field: &'static str) -> Result<N, ReconstructError> {
121 self.optional_field(field)
122 .ok_or_else(|| ReconstructError::MissingField {
123 node: self.parent.clone(),
124 field,
125 })
126 }
127
128 pub fn optional_field(&mut self, field: &'static str) -> Option<N> {
130 self.take_first(|child| !child.node.is_extra() && child.field == Some(field))
131 }
132
133 pub fn repeated_field(&mut self, field: &'static str) -> Vec<N> {
135 self.take_all(|child| !child.node.is_extra() && child.field == Some(field))
136 }
137
138 pub fn required_matching<T>(&mut self) -> Result<N, ReconstructError>
145 where
146 T: Reconstruct<N>,
147 {
148 self.optional_matching::<T>()
149 .ok_or_else(|| ReconstructError::MissingChild {
150 node: self.parent.clone(),
151 expected: std::any::type_name::<T>(),
152 })
153 }
154
155 pub fn optional_matching<T>(&mut self) -> Option<N>
157 where
158 T: Reconstruct<N>,
159 {
160 self.take_first(|child| {
161 !child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
162 })
163 }
164
165 pub fn repeated_matching<T>(&mut self) -> Vec<N>
167 where
168 T: Reconstruct<N>,
169 {
170 self.take_all(|child| {
171 !child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
172 })
173 }
174
175 pub fn required_anonymous(&mut self, spelling: &'static str) -> Result<N, ReconstructError> {
181 self.take_first(|child| {
182 child.field.is_none() && child.node.identity().matches(spelling, false)
183 })
184 .ok_or_else(|| ReconstructError::MissingChild {
185 node: self.parent.clone(),
186 expected: spelling,
187 })
188 }
189
190 pub fn optional_anonymous(&mut self, spelling: &'static str) -> Option<N> {
192 self.take_first(|child| {
193 child.field.is_none() && child.node.identity().matches(spelling, false)
194 })
195 }
196
197 pub fn required_delimiter(&mut self) -> Result<Delimiter, ReconstructError> {
204 for kind in [
205 DelimiterKind::Parenthesis,
206 DelimiterKind::Bracket,
207 DelimiterKind::Brace,
208 DelimiterKind::AngleBar,
209 ] {
210 let Some(opening) = self.optional_anonymous(kind.opening()) else {
211 continue;
212 };
213 let closing = self.required_anonymous(kind.closing())?;
214 return Ok(Delimiter::new(
215 kind,
216 DoubleSpan::new(opening.span(), closing.span()),
217 ));
218 }
219 Err(ReconstructError::MissingChild {
220 node: self.parent.clone(),
221 expected: "a delimiter",
222 })
223 }
224
225 pub fn punctuated(
234 &mut self,
235 values: Vec<N>,
236 spelling: &'static str,
237 ) -> Result<PunctuatedNodes<N>, ReconstructError> {
238 let punctuation = self.take_all(|child| {
239 child.field.is_none() && child.node.identity().matches(spelling, false)
240 });
241 let value_count = values.len();
242 let punctuation_count = punctuation.len();
243 let mut values = values.into_iter();
244 let Some(first) = values.next() else {
245 return Err(ReconstructError::InvalidPunctuated {
246 node: self.parent.clone(),
247 values: value_count,
248 punctuation: punctuation_count,
249 });
250 };
251 if punctuation_count + 1 != value_count {
252 return Err(ReconstructError::InvalidPunctuated {
253 node: self.parent.clone(),
254 values: value_count,
255 punctuation: punctuation_count,
256 });
257 }
258 Ok((first, punctuation.into_iter().zip(values).collect()))
259 }
260
261 pub fn optional_punctuated(
269 &mut self,
270 values: Vec<N>,
271 spelling: &'static str,
272 ) -> Result<Option<PunctuatedNodes<N>>, ReconstructError> {
273 if values.is_empty() {
274 let punctuation = self.take_all(|child| {
275 child.field.is_none() && child.node.identity().matches(spelling, false)
276 });
277 return if punctuation.is_empty() {
278 Ok(None)
279 } else {
280 Err(ReconstructError::InvalidPunctuated {
281 node: self.parent.clone(),
282 values: 0,
283 punctuation: punctuation.len(),
284 })
285 };
286 }
287 self.punctuated(values, spelling).map(Some)
288 }
289
290 fn take_first(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Option<N> {
291 self.children
292 .iter_mut()
293 .find_map(|child| child.take_if(|child| predicate(child)))
294 .map(|child| child.node)
295 }
296
297 fn take_all(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Vec<N> {
298 self.children
299 .iter_mut()
300 .filter_map(|child| {
301 child
302 .take_if(|child| predicate(child))
303 .map(|child| child.node)
304 })
305 .collect()
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq)]
310pub enum ReconstructError {
312 WrongNode {
314 expected: &'static str,
316 expected_named: bool,
318 actual: String,
320 actual_named: bool,
322 },
323 WrongCategory {
325 category: &'static str,
327 actual: String,
329 actual_named: bool,
331 },
332 MissingField {
334 node: String,
336 field: &'static str,
338 },
339 MissingChild {
341 node: String,
343 expected: &'static str,
345 },
346 InvalidPunctuated {
349 node: String,
351 values: usize,
353 punctuation: usize,
355 },
356}
357
358impl ReconstructError {
359 pub fn wrong_node(
361 expected: &'static str,
362 expected_named: bool,
363 actual: CSTNodeClassLabel<'_>,
364 ) -> Self {
365 Self::WrongNode {
366 expected,
367 expected_named,
368 actual: actual.name.into_owned(),
369 actual_named: actual.named,
370 }
371 }
372
373 pub fn wrong_category(category: &'static str, actual: CSTNodeClassLabel<'_>) -> Self {
375 Self::WrongCategory {
376 category,
377 actual: actual.name.into_owned(),
378 actual_named: actual.named,
379 }
380 }
381}
382
383impl Display for ReconstructError {
384 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
385 match self {
386 Self::WrongNode {
387 expected,
388 expected_named,
389 actual,
390 actual_named,
391 } => write!(
392 formatter,
393 "expected {} node {expected:?}, found {} node {actual:?}",
394 visibility(*expected_named),
395 visibility(*actual_named),
396 ),
397 Self::WrongCategory {
398 category,
399 actual,
400 actual_named,
401 } => write!(
402 formatter,
403 "node {} {actual:?} does not belong to {category}",
404 visibility(*actual_named),
405 ),
406 Self::MissingField { node, field } => {
407 write!(formatter, "node {node:?} has no {field:?} field")
408 }
409 Self::MissingChild { node, expected } => {
410 write!(formatter, "node {node:?} has no child matching {expected}")
411 }
412 Self::InvalidPunctuated {
413 node,
414 values,
415 punctuation,
416 } => write!(
417 formatter,
418 "node {node:?} has {values} punctuated values but {punctuation} separators",
419 ),
420 }
421 }
422}
423
424impl Error for ReconstructError {}
425
426fn visibility(named: bool) -> &'static str {
427 if named { "named" } else { "anonymous" }
428}