1use std::borrow::Cow;
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13
14use crate::{
15 Delimiter, DelimiterKind, DoubleSpan, Punctuated, Spanned, Terminal, TokenTree, TrailingTrivia,
16};
17
18type PunctuatedNodes<N> = Punctuated<N, N>;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct CSTNodeClassLabel<'syntax> {
24 pub name: Cow<'syntax, str>,
26 pub named: bool,
28}
29
30impl<'syntax> CSTNodeClassLabel<'syntax> {
31 pub fn new(name: impl Into<Cow<'syntax, str>>, named: bool) -> Self {
33 Self {
34 name: name.into(),
35 named,
36 }
37 }
38
39 pub fn matches(&self, name: &str, named: bool) -> bool {
41 self.named == named && self.name == name
42 }
43}
44
45pub struct ExternalCstChild<N> {
47 pub field: Option<&'static str>,
49 pub node: N,
51}
52
53pub trait ExternalCstNode: Sized + Spanned {
58 type Children<'syntax>: Iterator<Item = ExternalCstChild<Self>>
60 where
61 Self: 'syntax;
62
63 fn identity(&self) -> CSTNodeClassLabel<'_>;
65 fn children(&self) -> Self::Children<'_>;
67 fn text(&self) -> Cow<'_, str>;
69 fn is_extra(&self) -> bool {
71 false
72 }
73}
74
75pub trait Token: Terminal + Sized {
80 const SPELLING: &'static str;
82
83 fn matches_token_tree(token: &TokenTree) -> bool;
85
86 fn from_token_tree(token: TokenTree) -> Option<Self>;
88
89 fn from_token_tree_with_trivia(
91 token: TokenTree,
92 trailing_trivia: TrailingTrivia,
93 ) -> Option<Self> {
94 let mut token = Self::from_token_tree(token)?;
95 *token.trailing_trivia_mut() = trailing_trivia;
96 Some(token)
97 }
98}
99
100pub trait Migrate<N: ExternalCstNode>: Sized {
102 fn matches(node: &N) -> bool;
104 fn migrate(node: N) -> Result<Self, MigrateError>;
111}
112
113pub struct ChildCursor<N> {
115 children: Vec<Option<ExternalCstChild<N>>>,
117 parent: String,
119}
120
121impl<N: ExternalCstNode> ChildCursor<N> {
122 pub fn new(parent: &N) -> Self {
124 Self {
125 children: parent.children().map(Some).collect(),
126 parent: parent.identity().name.into_owned(),
127 }
128 }
129
130 pub fn required_field(&mut self, field: &'static str) -> Result<N, MigrateError> {
136 self.optional_field(field)
137 .ok_or_else(|| MigrateError::MissingField {
138 node: self.parent.clone(),
139 field,
140 })
141 }
142
143 pub fn optional_field(&mut self, field: &'static str) -> Option<N> {
145 self.take_first(|child| !child.node.is_extra() && child.field == Some(field))
146 }
147
148 pub fn repeated_field(&mut self, field: &'static str) -> Vec<N> {
150 self.take_all(|child| !child.node.is_extra() && child.field == Some(field))
151 }
152
153 pub fn required_matching<T>(&mut self) -> Result<N, MigrateError>
160 where
161 T: Migrate<N>,
162 {
163 self.optional_matching::<T>()
164 .ok_or_else(|| MigrateError::MissingChild {
165 node: self.parent.clone(),
166 expected: std::any::type_name::<T>(),
167 })
168 }
169
170 pub fn optional_matching<T>(&mut self) -> Option<N>
172 where
173 T: Migrate<N>,
174 {
175 self.take_first(|child| {
176 !child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
177 })
178 }
179
180 pub fn repeated_matching<T>(&mut self) -> Vec<N>
182 where
183 T: Migrate<N>,
184 {
185 self.take_all(|child| {
186 !child.node.is_extra() && child.field.is_none() && T::matches(&child.node)
187 })
188 }
189
190 pub fn required_anonymous(&mut self, spelling: &'static str) -> Result<N, MigrateError> {
196 self.take_first(|child| {
197 child.field.is_none() && child.node.identity().matches(spelling, false)
198 })
199 .ok_or_else(|| MigrateError::MissingChild {
200 node: self.parent.clone(),
201 expected: spelling,
202 })
203 }
204
205 pub fn optional_anonymous(&mut self, spelling: &'static str) -> Option<N> {
207 self.take_first(|child| {
208 child.field.is_none() && child.node.identity().matches(spelling, false)
209 })
210 }
211
212 pub fn required_delimiter(&mut self) -> Result<Delimiter, MigrateError> {
219 for kind in [
220 DelimiterKind::Parenthesis,
221 DelimiterKind::Bracket,
222 DelimiterKind::Brace,
223 DelimiterKind::AngleBar,
224 ] {
225 let Some(opening) = self.optional_anonymous(kind.opening()) else {
226 continue;
227 };
228 let closing = self.required_anonymous(kind.closing())?;
229 return Ok(Delimiter::new(
230 kind,
231 DoubleSpan::new(opening.expect_span(), closing.expect_span()),
232 ));
233 }
234 Err(MigrateError::MissingChild {
235 node: self.parent.clone(),
236 expected: "a delimiter",
237 })
238 }
239
240 pub fn punctuated(
249 &mut self,
250 values: Vec<N>,
251 spelling: &'static str,
252 ) -> Result<PunctuatedNodes<N>, MigrateError> {
253 let punctuation = self.take_all(|child| {
254 child.field.is_none() && child.node.identity().matches(spelling, false)
255 });
256 let value_count = values.len();
257 let punctuation_count = punctuation.len();
258 let mut values = values.into_iter();
259 let Some(first) = values.next() else {
260 return Err(MigrateError::InvalidPunctuated {
261 node: self.parent.clone(),
262 values: value_count,
263 punctuation: punctuation_count,
264 });
265 };
266 if punctuation_count == 0 || punctuation_count + 1 != value_count {
267 return Err(MigrateError::InvalidPunctuated {
268 node: self.parent.clone(),
269 values: value_count,
270 punctuation: punctuation_count,
271 });
272 }
273 let mut rest = punctuation.into_iter().zip(values);
274 let Some((first_punctuation, second)) = rest.next() else {
275 return Err(MigrateError::InvalidPunctuated {
276 node: self.parent.clone(),
277 values: value_count,
278 punctuation: punctuation_count,
279 });
280 };
281 let mut punctuated = Punctuated::new(first, first_punctuation, second);
282 for (punctuation, value) in rest {
283 punctuated.push(punctuation, value);
284 }
285 Ok(punctuated)
286 }
287
288 pub fn optional_punctuated(
296 &mut self,
297 values: Vec<N>,
298 spelling: &'static str,
299 ) -> Result<Option<PunctuatedNodes<N>>, MigrateError> {
300 if values.is_empty() {
301 let punctuation = self.take_all(|child| {
302 child.field.is_none() && child.node.identity().matches(spelling, false)
303 });
304 return if punctuation.is_empty() {
305 Ok(None)
306 } else {
307 Err(MigrateError::InvalidPunctuated {
308 node: self.parent.clone(),
309 values: 0,
310 punctuation: punctuation.len(),
311 })
312 };
313 }
314 self.punctuated(values, spelling).map(Some)
315 }
316
317 fn take_first(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Option<N> {
319 self.children
320 .iter_mut()
321 .find_map(|child| child.take_if(|child| predicate(child)))
322 .map(|child| child.node)
323 }
324
325 fn take_all(&mut self, predicate: impl Fn(&ExternalCstChild<N>) -> bool) -> Vec<N> {
327 self.children
328 .iter_mut()
329 .filter_map(|child| {
330 child
331 .take_if(|child| predicate(child))
332 .map(|child| child.node)
333 })
334 .collect()
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, Eq)]
339pub enum MigrateError {
341 WrongNode {
343 expected: &'static str,
345 expected_named: bool,
347 actual: String,
349 actual_named: bool,
351 },
352 WrongCategory {
354 category: &'static str,
356 actual: String,
358 actual_named: bool,
360 },
361 MissingField {
363 node: String,
365 field: &'static str,
367 },
368 MissingChild {
370 node: String,
372 expected: &'static str,
374 },
375 InvalidPunctuated {
378 node: String,
380 values: usize,
382 punctuation: usize,
384 },
385}
386
387impl MigrateError {
388 pub fn wrong_node(
390 expected: &'static str,
391 expected_named: bool,
392 actual: CSTNodeClassLabel<'_>,
393 ) -> Self {
394 Self::WrongNode {
395 expected,
396 expected_named,
397 actual: actual.name.into_owned(),
398 actual_named: actual.named,
399 }
400 }
401
402 pub fn wrong_category(category: &'static str, actual: CSTNodeClassLabel<'_>) -> Self {
404 Self::WrongCategory {
405 category,
406 actual: actual.name.into_owned(),
407 actual_named: actual.named,
408 }
409 }
410}
411
412impl Display for MigrateError {
413 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
414 match self {
415 Self::WrongNode {
416 expected,
417 expected_named,
418 actual,
419 actual_named,
420 } => write!(
421 formatter,
422 "expected {} node {expected:?}, found {} node {actual:?}",
423 visibility(*expected_named),
424 visibility(*actual_named),
425 ),
426 Self::WrongCategory {
427 category,
428 actual,
429 actual_named,
430 } => write!(
431 formatter,
432 "node {} {actual:?} does not belong to {category}",
433 visibility(*actual_named),
434 ),
435 Self::MissingField { node, field } => {
436 write!(formatter, "node {node:?} has no {field:?} field")
437 }
438 Self::MissingChild { node, expected } => {
439 write!(formatter, "node {node:?} has no child matching {expected}")
440 }
441 Self::InvalidPunctuated {
442 node,
443 values,
444 punctuation,
445 } => write!(
446 formatter,
447 "node {node:?} has {values} punctuated values but {punctuation} separators",
448 ),
449 }
450 }
451}
452
453impl Error for MigrateError {}
454
455fn visibility(named: bool) -> &'static str {
457 if named { "named" } else { "anonymous" }
458}