1use std::borrow::Cow;
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13
14use crate::{Delimiter, DelimiterKind, DoubleSpan, Spanned, Terminal, TokenTree, TrailingTrivia};
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: Terminal + 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 fn from_token_tree_with_trivia(
88 token: TokenTree,
89 trailing_trivia: TrailingTrivia,
90 ) -> Option<Self> {
91 let mut token = Self::from_token_tree(token)?;
92 *token.trailing_trivia_mut() = trailing_trivia;
93 Some(token)
94 }
95}
96
97pub trait Migrate<N: ExternalCstNode>: Sized {
99 fn matches(node: &N) -> bool;
101 fn migrate(node: N) -> Result<Self, MigrateError>;
108}
109
110pub struct ChildCursor<N> {
112 children: Vec<Option<ExternalCstChild<N>>>,
113 parent: String,
114}
115
116impl<N: ExternalCstNode> ChildCursor<N> {
117 pub fn new(parent: &N) -> Self {
119 Self {
120 children: parent.children().map(Some).collect(),
121 parent: parent.identity().name.into_owned(),
122 }
123 }
124
125 pub fn required_field(&mut self, field: &'static str) -> Result<N, MigrateError> {
131 self.optional_field(field)
132 .ok_or_else(|| MigrateError::MissingField {
133 node: self.parent.clone(),
134 field,
135 })
136 }
137
138 pub fn optional_field(&mut self, field: &'static str) -> Option<N> {
140 self.take_first(|child| !child.node.is_extra() && child.field == Some(field))
141 }
142
143 pub fn repeated_field(&mut self, field: &'static str) -> Vec<N> {
145 self.take_all(|child| !child.node.is_extra() && child.field == Some(field))
146 }
147
148 pub fn required_matching<T>(&mut self) -> Result<N, MigrateError>
155 where
156 T: Migrate<N>,
157 {
158 self.optional_matching::<T>()
159 .ok_or_else(|| MigrateError::MissingChild {
160 node: self.parent.clone(),
161 expected: std::any::type_name::<T>(),
162 })
163 }
164
165 pub fn optional_matching<T>(&mut self) -> Option<N>
167 where
168 T: Migrate<N>,
169 {
170 self.take_first(|child| {
171 !child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
172 })
173 }
174
175 pub fn repeated_matching<T>(&mut self) -> Vec<N>
177 where
178 T: Migrate<N>,
179 {
180 self.take_all(|child| {
181 !child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
182 })
183 }
184
185 pub fn required_anonymous(&mut self, spelling: &'static str) -> Result<N, MigrateError> {
191 self.take_first(|child| {
192 child.field.is_none() && child.node.identity().matches(spelling, false)
193 })
194 .ok_or_else(|| MigrateError::MissingChild {
195 node: self.parent.clone(),
196 expected: spelling,
197 })
198 }
199
200 pub fn optional_anonymous(&mut self, spelling: &'static str) -> Option<N> {
202 self.take_first(|child| {
203 child.field.is_none() && child.node.identity().matches(spelling, false)
204 })
205 }
206
207 pub fn required_delimiter(&mut self) -> Result<Delimiter, MigrateError> {
214 for kind in [
215 DelimiterKind::Parenthesis,
216 DelimiterKind::Bracket,
217 DelimiterKind::Brace,
218 DelimiterKind::AngleBar,
219 ] {
220 let Some(opening) = self.optional_anonymous(kind.opening()) else {
221 continue;
222 };
223 let closing = self.required_anonymous(kind.closing())?;
224 return Ok(Delimiter::new(
225 kind,
226 DoubleSpan::new(opening.expect_span(), closing.expect_span()),
227 ));
228 }
229 Err(MigrateError::MissingChild {
230 node: self.parent.clone(),
231 expected: "a delimiter",
232 })
233 }
234
235 pub fn punctuated(
244 &mut self,
245 values: Vec<N>,
246 spelling: &'static str,
247 ) -> Result<PunctuatedNodes<N>, MigrateError> {
248 let punctuation = self.take_all(|child| {
249 child.field.is_none() && child.node.identity().matches(spelling, false)
250 });
251 let value_count = values.len();
252 let punctuation_count = punctuation.len();
253 let mut values = values.into_iter();
254 let Some(first) = values.next() else {
255 return Err(MigrateError::InvalidPunctuated {
256 node: self.parent.clone(),
257 values: value_count,
258 punctuation: punctuation_count,
259 });
260 };
261 if punctuation_count + 1 != value_count {
262 return Err(MigrateError::InvalidPunctuated {
263 node: self.parent.clone(),
264 values: value_count,
265 punctuation: punctuation_count,
266 });
267 }
268 Ok((first, punctuation.into_iter().zip(values).collect()))
269 }
270
271 pub fn optional_punctuated(
279 &mut self,
280 values: Vec<N>,
281 spelling: &'static str,
282 ) -> Result<Option<PunctuatedNodes<N>>, MigrateError> {
283 if values.is_empty() {
284 let punctuation = self.take_all(|child| {
285 child.field.is_none() && child.node.identity().matches(spelling, false)
286 });
287 return if punctuation.is_empty() {
288 Ok(None)
289 } else {
290 Err(MigrateError::InvalidPunctuated {
291 node: self.parent.clone(),
292 values: 0,
293 punctuation: punctuation.len(),
294 })
295 };
296 }
297 self.punctuated(values, spelling).map(Some)
298 }
299
300 fn take_first(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Option<N> {
301 self.children
302 .iter_mut()
303 .find_map(|child| child.take_if(|child| predicate(child)))
304 .map(|child| child.node)
305 }
306
307 fn take_all(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Vec<N> {
308 self.children
309 .iter_mut()
310 .filter_map(|child| {
311 child
312 .take_if(|child| predicate(child))
313 .map(|child| child.node)
314 })
315 .collect()
316 }
317}
318
319#[derive(Debug, Clone, PartialEq, Eq)]
320pub enum MigrateError {
322 WrongNode {
324 expected: &'static str,
326 expected_named: bool,
328 actual: String,
330 actual_named: bool,
332 },
333 WrongCategory {
335 category: &'static str,
337 actual: String,
339 actual_named: bool,
341 },
342 MissingField {
344 node: String,
346 field: &'static str,
348 },
349 MissingChild {
351 node: String,
353 expected: &'static str,
355 },
356 InvalidPunctuated {
359 node: String,
361 values: usize,
363 punctuation: usize,
365 },
366}
367
368impl MigrateError {
369 pub fn wrong_node(
371 expected: &'static str,
372 expected_named: bool,
373 actual: CSTNodeClassLabel<'_>,
374 ) -> Self {
375 Self::WrongNode {
376 expected,
377 expected_named,
378 actual: actual.name.into_owned(),
379 actual_named: actual.named,
380 }
381 }
382
383 pub fn wrong_category(category: &'static str, actual: CSTNodeClassLabel<'_>) -> Self {
385 Self::WrongCategory {
386 category,
387 actual: actual.name.into_owned(),
388 actual_named: actual.named,
389 }
390 }
391}
392
393impl Display for MigrateError {
394 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
395 match self {
396 Self::WrongNode {
397 expected,
398 expected_named,
399 actual,
400 actual_named,
401 } => write!(
402 formatter,
403 "expected {} node {expected:?}, found {} node {actual:?}",
404 visibility(*expected_named),
405 visibility(*actual_named),
406 ),
407 Self::WrongCategory {
408 category,
409 actual,
410 actual_named,
411 } => write!(
412 formatter,
413 "node {} {actual:?} does not belong to {category}",
414 visibility(*actual_named),
415 ),
416 Self::MissingField { node, field } => {
417 write!(formatter, "node {node:?} has no {field:?} field")
418 }
419 Self::MissingChild { node, expected } => {
420 write!(formatter, "node {node:?} has no child matching {expected}")
421 }
422 Self::InvalidPunctuated {
423 node,
424 values,
425 punctuation,
426 } => write!(
427 formatter,
428 "node {node:?} has {values} punctuated values but {punctuation} separators",
429 ),
430 }
431 }
432}
433
434impl Error for MigrateError {}
435
436fn visibility(named: bool) -> &'static str {
437 if named { "named" } else { "anonymous" }
438}