1use core::hash::Hash;
2
3use cairo_lang_filesystem::db::FilesGroup;
4use cairo_lang_filesystem::ids::{FileId, SmolStrId};
5use cairo_lang_filesystem::span::{TextOffset, TextPosition, TextSpan, TextWidth};
6use cairo_lang_proc_macros::{DebugWithDb, HeapSize};
7use cairo_lang_utils::require;
8use salsa::Database;
9use salsa::plumbing::AsId;
10
11use self::ast::TriviaGreen;
12use self::green::GreenNode;
13use self::ids::{GreenId, SyntaxStablePtrId};
14use self::kind::SyntaxKind;
15use crate::node::db::SyntaxGroup;
16use crate::node::iter::{Preorder, WalkEvent};
17
18pub mod ast;
19pub mod db;
20pub mod element_list;
21pub mod green;
22pub mod helpers;
23pub mod ids;
24pub mod iter;
25pub mod key_fields;
26pub mod kind;
27pub mod stable_ptr;
28pub mod with_db;
29
30#[cfg(test)]
31mod ast_test;
32#[cfg(test)]
33mod test_utils;
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash, DebugWithDb, salsa::Update)]
37#[debug_db(dyn Database)]
38pub enum SyntaxNodeId<'db> {
39 Root(FileId<'db>),
40 Child {
41 parent: SyntaxNode<'db>,
43 index: usize,
45 key_fields: Box<[GreenId<'db>]>,
48 },
49}
50
51#[salsa::tracked]
54#[derive(Debug, HeapSize)]
55struct SyntaxNodeData<'a> {
56 #[tracked]
57 green: GreenId<'a>,
58 #[tracked]
61 offset: TextOffset,
62 #[returns(ref)]
64 id: SyntaxNodeId<'a>,
65}
66
67impl<'db> SyntaxNodeData<'db> {
68 pub fn parent(&self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
70 match self.id(db) {
71 SyntaxNodeId::Root(_) => None,
72 SyntaxNodeId::Child { parent, .. } => Some(*parent),
73 }
74 }
75}
76
77impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNodeData<'db> {
78 type Db = dyn Database;
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
80 f.debug_struct("SyntaxNode")
81 .field("green", &self.green(db).debug(db))
82 .field("offset", &self.offset(db))
83 .field("id", &self.id(db).debug(db))
84 .finish()
85 }
86}
87
88#[derive(Clone, Copy, PartialEq, Eq, Hash, salsa::Update, HeapSize)]
94pub struct SyntaxNode<'a> {
95 data: SyntaxNodeData<'a>,
96 parent: Option<SyntaxNodeData<'a>>,
98 kind: SyntaxKind,
100 parent_kind: Option<SyntaxKind>,
102}
103
104impl<'db> std::fmt::Debug for SyntaxNode<'db> {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 write!(f, "SyntaxNode({:x})", self.data.as_id().index())
107 }
108}
109
110impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNode<'db> {
111 type Db = dyn Database;
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
113 self.data.fmt(f, db)
114 }
115}
116
117impl<'db> SyntaxNode<'db> {
118 pub fn offset(self, db: &'db dyn Database) -> TextOffset {
120 self.data.offset(db)
121 }
122
123 pub fn parent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
125 match self.data.id(db) {
126 SyntaxNodeId::Child { parent, .. } => Some(*parent),
127 SyntaxNodeId::Root(_) => None,
128 }
129 }
130
131 pub fn grandparent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
134 self.parent?.parent(db)
135 }
136
137 pub fn is_root(self) -> bool {
139 self.parent.is_none()
140 }
141
142 pub fn stable_ptr(self, _db: &'db dyn Database) -> SyntaxStablePtrId<'db> {
144 SyntaxStablePtrId(self)
145 }
146
147 pub fn raw_id(&self, db: &'db dyn Database) -> &'db SyntaxNodeId<'db> {
148 self.data.id(db)
149 }
150
151 pub fn key_fields(self, db: &'db dyn Database) -> &'db [GreenId<'db>] {
153 match self.data.id(db) {
154 SyntaxNodeId::Child { key_fields, .. } => key_fields,
155 SyntaxNodeId::Root(_) => &[],
156 }
157 }
158
159 pub fn file_id(&self, db: &'db dyn Database) -> FileId<'db> {
161 if let Some(parent_data) = self.parent {
163 return match parent_data.id(db) {
164 SyntaxNodeId::Child { parent, .. } => parent.file_id(db),
165 SyntaxNodeId::Root(file_id) => *file_id,
166 };
167 }
168 match self.data.id(db) {
169 SyntaxNodeId::Root(file_id) => *file_id,
170 SyntaxNodeId::Child { .. } => {
171 unreachable!("Parent already checked and found to not exist.")
172 }
173 }
174 }
175
176 pub fn nth_parent<'r: 'db>(&self, db: &'r dyn Database, mut n: usize) -> SyntaxNode<'db> {
183 let mut result = Some(*self);
184 while let Some(p) = result
185 && n > 1
186 {
187 result = p.grandparent(db);
188 n -= 2;
189 }
190 if let Some(p) = result
191 && n == 1
192 {
193 result = p.parent(db);
194 }
195 result.unwrap_or_else(|| {
196 panic!(
197 "N'th parent did not exist. File {} Offset {:?}",
198 self.file_id(db).long(db).full_path(db),
199 self.offset(db)
200 )
201 })
202 }
203}
204
205pub fn new_syntax_node<'db>(
207 db: &'db dyn Database,
208 green: GreenId<'db>,
209 offset: TextOffset,
210 id: SyntaxNodeId<'db>,
211 kind: SyntaxKind,
212) -> SyntaxNode<'db> {
213 let (parent, parent_kind) = match &id {
214 SyntaxNodeId::Child { parent, .. } => (Some(parent.data), Some(parent.kind)),
215 SyntaxNodeId::Root(_) => (None, None),
216 };
217 let data = SyntaxNodeData::new(db, green, offset, id);
218 SyntaxNode { data, parent, kind, parent_kind }
219}
220
221#[salsa::tracked]
223fn new_root_node<'db>(
224 db: &'db dyn Database,
225 file_id: FileId<'db>,
226 green: GreenId<'db>,
227 offset: TextOffset,
228) -> SyntaxNode<'db> {
229 let kind = green.long(db).kind;
230 new_syntax_node(db, green, offset, SyntaxNodeId::Root(file_id), kind)
231}
232
233impl<'a> SyntaxNode<'a> {
235 pub fn new_root(db: &'a dyn Database, file_id: FileId<'a>, green: GreenId<'a>) -> Self {
237 new_root_node(db, file_id, green, TextOffset::START)
238 }
239
240 pub fn new_root_with_offset(
242 db: &'a dyn Database,
243 file_id: FileId<'a>,
244 green: GreenId<'a>,
245 initial_offset: Option<TextOffset>,
246 ) -> Self {
247 new_root_node(db, file_id, green, initial_offset.unwrap_or_default())
248 }
249
250 pub fn width(&self, db: &dyn Database) -> TextWidth {
254 self.green_node(db).width(db)
255 }
256
257 pub fn kind(&self, _db: &dyn Database) -> SyntaxKind {
259 self.kind
260 }
261
262 pub fn span(&self, db: &dyn Database) -> TextSpan {
264 TextSpan::new_with_width(self.offset(db), self.width(db))
265 }
266
267 pub fn text(&self, db: &'a dyn Database) -> Option<SmolStrId<'a>> {
269 match &self.green_node(db).details {
270 green::GreenNodeDetails::Token(text) => Some(*text),
271 green::GreenNodeDetails::Node { .. } => None,
272 }
273 }
274
275 pub fn green_node(&self, db: &'a dyn Database) -> &'a GreenNode<'a> {
277 self.data.green(db).long(db)
278 }
279
280 pub fn span_without_trivia(&self, db: &dyn Database) -> TextSpan {
282 let green_node = self.green_node(db);
283 let (leading, trailing) = both_trivia_width(db, green_node);
284 let offset = self.offset(db);
285 let start = offset.add_width(leading);
286 let end = offset.add_width(green_node.width(db)).sub_width(trailing);
287 TextSpan::new(start, end)
288 }
289
290 pub fn get_terminal_token(&'a self, db: &'a dyn Database) -> Option<SyntaxNode<'a>> {
293 let green_node = self.green_node(db);
294 require(green_node.kind.is_terminal())?;
295 self.get_children(db).get(1).copied()
297 }
298
299 pub fn get_children(&self, db: &'a dyn Database) -> &'a [SyntaxNode<'a>] {
303 db.get_children(*self)
304 }
305
306 pub(crate) fn get_children_impl(&self, db: &'a dyn Database) -> Vec<SyntaxNode<'a>> {
308 let mut offset = self.offset(db);
309 let self_green = self.green_node(db);
310 let children = self_green.children();
311 let mut res: Vec<SyntaxNode<'_>> = Vec::with_capacity(children.len());
312 let mut key_map = Vec::<(_, usize)>::with_capacity(children.len());
313 for green_id in children {
314 let green = green_id.long(db);
315 let width = green.width(db);
316 let kind = green.kind;
317 let rng = key_fields::key_fields_range(kind);
318 let key_fields: &'a [GreenId<'a>] = &green.children()[rng];
319 let index = key_map
320 .iter_mut()
321 .find(|(k, _v)| *k == key_fields)
322 .map(|(_k, v)| {
323 let index = *v;
324 *v += 1;
325 index
326 })
327 .unwrap_or_else(|| {
328 key_map.push((key_fields, 1));
329 0
330 });
331 res.push(new_syntax_node(
333 db,
334 *green_id,
335 offset,
336 SyntaxNodeId::Child { parent: *self, index, key_fields: Box::from(key_fields) },
337 kind,
338 ));
339
340 offset = offset.add_width(width);
341 }
342 res
343 }
344
345 pub fn span_start_without_trivia(&self, db: &dyn Database) -> TextOffset {
349 let green_node = self.green_node(db);
350 let leading = leading_trivia_width(db, green_node);
351 self.offset(db).add_width(leading)
352 }
353
354 pub fn span_end_without_trivia(&self, db: &dyn Database) -> TextOffset {
356 let green_node = self.green_node(db);
357 let trailing = trailing_trivia_width(db, green_node);
358 self.offset(db).add_width(green_node.width(db)).sub_width(trailing)
359 }
360
361 pub fn lookup_offset(&self, db: &'a dyn Database, offset: TextOffset) -> SyntaxNode<'a> {
363 for child in self.get_children(db).iter() {
364 if child.offset(db).add_width(child.width(db)) > offset {
365 return child.lookup_offset(db, offset);
366 }
367 }
368 *self
369 }
370
371 pub fn lookup_position(&self, db: &'a dyn Database, position: TextPosition) -> SyntaxNode<'a> {
373 match position.offset_in_file(db, self.stable_ptr(db).file_id(db)) {
374 Some(offset) => self.lookup_offset(db, offset),
375 None => *self,
376 }
377 }
378
379 pub fn get_text(&self, db: &'a dyn Database) -> &'a str {
381 let file_content =
385 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
386
387 self.span(db).take(file_content)
388 }
389
390 pub fn get_text_without_inner_commentable_children(&self, db: &dyn Database) -> String {
395 let mut buffer = String::new();
396
397 match &self.green_node(db).details {
398 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
399 green::GreenNodeDetails::Node { .. } => {
400 for child in self.get_children(db).iter() {
401 let kind = child.kind(db);
402
403 if !matches!(
406 kind,
407 SyntaxKind::FunctionWithBody
408 | SyntaxKind::ItemModule
409 | SyntaxKind::TraitItemFunction
410 ) {
411 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
412 child, db,
413 ));
414 }
415 }
416 }
417 }
418 buffer
419 }
420
421 pub fn get_text_without_all_comment_trivia(&self, db: &dyn Database) -> String {
424 let mut buffer = String::new();
425
426 match &self.green_node(db).details {
427 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
428 green::GreenNodeDetails::Node { .. } => {
429 for child in self.get_children(db).iter() {
430 if let Some(trivia) = ast::Trivia::cast(db, *child) {
431 trivia.elements(db).for_each(|element| {
432 if !matches!(
433 element,
434 ast::Trivium::SingleLineComment(_)
435 | ast::Trivium::SingleLineDocComment(_)
436 | ast::Trivium::SingleLineInnerComment(_)
437 ) {
438 buffer.push_str(
439 &element
440 .as_syntax_node()
441 .get_text_without_all_comment_trivia(db),
442 );
443 }
444 });
445 } else {
446 buffer
447 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
448 }
449 }
450 }
451 }
452 buffer
453 }
454
455 pub fn get_text_without_trivia(self, db: &'a dyn Database) -> SmolStrId<'a> {
460 let file_content =
461 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
462 SmolStrId::from(db, self.span_without_trivia(db).take(file_content))
463 }
464
465 pub fn get_text_of_span(self, db: &'a dyn Database, span: TextSpan) -> &'a str {
471 assert!(self.span(db).contains(span));
472 let file_content =
473 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
474 span.take(file_content)
475 }
476
477 pub fn descendants(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
484 self.preorder(db).filter_map(|event| match event {
485 WalkEvent::Enter(node) => Some(node),
486 WalkEvent::Leave(_) => None,
487 })
488 }
489
490 pub fn preorder(&self, db: &'a dyn Database) -> Preorder<'a> {
493 Preorder::new(*self, db)
494 }
495
496 pub fn tokens(&self, db: &'a dyn Database) -> impl Iterator<Item = Self> + 'a {
498 self.preorder(db).filter_map(|event| match event {
499 WalkEvent::Enter(node) if node.kind(db).is_terminal() => Some(node),
500 _ => None,
501 })
502 }
503
504 pub fn cast<T: TypedSyntaxNode<'a>>(self, db: &'a dyn Database) -> Option<T> {
506 T::cast(db, self)
507 }
508
509 pub fn ancestors(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
513 std::iter::successors(self.parent(db), |n| n.parent(db))
515 }
516
517 pub fn ancestors_with_self(
519 &self,
520 db: &'a dyn Database,
521 ) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
522 std::iter::successors(Some(*self), |n| n.parent(db))
523 }
524
525 pub fn is_ancestor(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
527 node.ancestors(db).any(|n| n == *self)
528 }
529
530 pub fn is_descendant(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
532 node.is_ancestor(db, self)
533 }
534
535 pub fn is_ancestor_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
537 node.ancestors_with_self(db).any(|n| n == *self)
538 }
539
540 pub fn is_descendant_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
542 node.is_ancestor_or_self(db, self)
543 }
544
545 pub fn ancestor_of_kind(
549 &self,
550 db: &'a dyn Database,
551 kind: SyntaxKind,
552 ) -> Option<SyntaxNode<'a>> {
553 self.ancestors(db).find(|node| node.kind(db) == kind)
554 }
555
556 pub fn ancestor_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
558 self.ancestors(db).find_map(|node| T::cast(db, node))
559 }
560
561 pub fn parent_of_kind(&self, db: &'a dyn Database, kind: SyntaxKind) -> Option<SyntaxNode<'a>> {
563 self.parent(db).filter(|node| node.kind(db) == kind)
564 }
565
566 pub fn parent_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
568 self.parent(db).and_then(|node| T::cast(db, node))
569 }
570
571 pub fn ancestor_of_kinds(
573 &self,
574 db: &'a dyn Database,
575 kinds: &[SyntaxKind],
576 ) -> Option<SyntaxNode<'a>> {
577 self.ancestors(db).find(|node| kinds.contains(&node.kind(db)))
578 }
579
580 pub fn parent_kind(&self, _db: &dyn Database) -> Option<SyntaxKind> {
582 self.parent_kind
583 }
584
585 pub fn grandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
587 self.parent(db)?.parent_kind(db)
588 }
589
590 pub fn grandgrandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
592 self.grandparent(db)?.parent_kind(db)
593 }
594}
595
596pub trait TypedSyntaxNode<'a>: Sized {
599 const OPTIONAL_KIND: Option<SyntaxKind>;
601 type StablePtr: TypedStablePtr<'a>;
602 type Green;
603 fn missing(db: &'a dyn Database) -> Self::Green;
604 fn from_syntax_node(db: &'a dyn Database, node: SyntaxNode<'a>) -> Self;
605 fn cast(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self>;
606 fn as_syntax_node(&self) -> SyntaxNode<'a>;
607 fn stable_ptr(&self, db: &'a dyn Database) -> Self::StablePtr;
608}
609
610pub trait Token<'a>: TypedSyntaxNode<'a> {
611 fn new_green(db: &'a dyn Database, text: SmolStrId<'a>) -> Self::Green;
612 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
613}
614
615pub trait Terminal<'a>: TypedSyntaxNode<'a> {
616 const KIND: SyntaxKind;
617 type TokenType: Token<'a>;
618 fn new_green(
619 db: &'a dyn Database,
620 leading_trivia: TriviaGreen<'a>,
621 token: <<Self as Terminal<'a>>::TokenType as TypedSyntaxNode<'a>>::Green,
622 trailing_trivia: TriviaGreen<'a>,
623 ) -> <Self as TypedSyntaxNode<'a>>::Green;
624 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
626 fn cast_token(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self> {
628 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
629 Some(Self::from_syntax_node(db, node.parent(db)?))
630 } else {
631 None
632 }
633 }
634}
635
636pub trait TypedStablePtr<'a> {
638 type SyntaxNode: TypedSyntaxNode<'a>;
639 fn lookup(&self, db: &'a dyn Database) -> Self::SyntaxNode;
641 fn untyped(self) -> SyntaxStablePtrId<'a>;
643}
644
645fn leading_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
647 match &green.details {
648 green::GreenNodeDetails::Token(_) => TextWidth::default(),
649 green::GreenNodeDetails::Node { children, width } => {
650 if *width == TextWidth::default() {
651 return TextWidth::default();
652 }
653 if green.kind.is_terminal() {
654 return children[0].long(db).width(db);
655 }
656 let non_empty = find_non_empty_child(db, &mut children.iter())
657 .expect("Parent width non-empty - one of the children should be non-empty");
658 leading_trivia_width(db, non_empty)
659 }
660 }
661}
662
663fn trailing_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
665 match &green.details {
666 green::GreenNodeDetails::Token(_) => TextWidth::default(),
667 green::GreenNodeDetails::Node { children, width } => {
668 if *width == TextWidth::default() {
669 return TextWidth::default();
670 }
671 if green.kind.is_terminal() {
672 return children[2].long(db).width(db);
673 }
674 let non_empty = find_non_empty_child(db, &mut children.iter().rev())
675 .expect("Parent width non-empty - one of the children should be non-empty");
676 trailing_trivia_width(db, non_empty)
677 }
678 }
679}
680
681fn both_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> (TextWidth, TextWidth) {
683 match &green.details {
684 green::GreenNodeDetails::Token(_) => (TextWidth::default(), TextWidth::default()),
685 green::GreenNodeDetails::Node { children, width } => {
686 if *width == TextWidth::default() {
687 return (TextWidth::default(), TextWidth::default());
688 }
689 if green.kind.is_terminal() {
690 return (children[0].long(db).width(db), children[2].long(db).width(db));
691 }
692 let mut iter = children.iter();
693 let first_non_empty = find_non_empty_child(db, &mut iter)
694 .expect("Parent width non-empty - one of the children should be non-empty");
695 if let Some(last_non_empty) = find_non_empty_child(db, &mut iter.rev()) {
696 (
697 leading_trivia_width(db, first_non_empty),
698 trailing_trivia_width(db, last_non_empty),
699 )
700 } else {
701 both_trivia_width(db, first_non_empty)
702 }
703 }
704 }
705}
706
707fn find_non_empty_child<'a>(
709 db: &'a dyn Database,
710 child_iter: &mut impl Iterator<Item = &'a GreenId<'a>>,
711) -> Option<&'a GreenNode<'a>> {
712 child_iter.find_map(|child| {
713 let child = child.long(db);
714 (child.width(db) != TextWidth::default()).then_some(child)
715 })
716}