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 kind: SyntaxKind,
46 index: usize,
48 key_fields: Box<[GreenId<'db>]>,
51 },
52}
53
54#[salsa::tracked]
57#[derive(Debug, HeapSize)]
58struct SyntaxNodeData<'a> {
59 #[tracked]
60 green: GreenId<'a>,
61 #[tracked]
65 offset_in_parent: TextOffset,
66 #[returns(ref)]
68 id: SyntaxNodeId<'a>,
69}
70
71impl<'db> SyntaxNodeData<'db> {
72 pub fn parent(&self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
74 match self.id(db) {
75 SyntaxNodeId::Root(_) => None,
76 SyntaxNodeId::Child { parent, .. } => Some(*parent),
77 }
78 }
79}
80
81#[salsa::tracked]
86fn absolute_offset<'db>(db: &'db dyn Database, data: SyntaxNodeData<'db>) -> TextOffset {
87 match data.parent(db) {
88 Some(parent) => absolute_offset(db, parent.data)
89 .add_width(data.offset_in_parent(db) - TextOffset::START),
90 None => data.offset_in_parent(db),
91 }
92}
93
94impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNodeData<'db> {
95 type Db = dyn Database;
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
97 f.debug_struct("SyntaxNode")
98 .field("green", &self.green(db).debug(db))
99 .field("offset_in_parent", &self.offset_in_parent(db))
100 .field("id", &self.id(db).debug(db))
101 .finish()
102 }
103}
104
105#[derive(Clone, Copy, PartialEq, Eq, Hash, salsa::Update, HeapSize)]
112pub struct SyntaxNode<'a> {
113 data: SyntaxNodeData<'a>,
114 parent: Option<SyntaxNodeData<'a>>,
116 kind: SyntaxKind,
118 parent_kind: Option<SyntaxKind>,
120}
121
122impl<'db> std::fmt::Debug for SyntaxNode<'db> {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 write!(f, "SyntaxNode({:x})", self.data.as_id().index())
125 }
126}
127
128impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNode<'db> {
129 type Db = dyn Database;
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
131 self.data.fmt(f, db)
132 }
133}
134
135impl<'db> SyntaxNode<'db> {
136 pub fn offset(self, db: &'db dyn Database) -> TextOffset {
138 absolute_offset(db, self.data)
139 }
140
141 pub fn parent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
143 match self.data.id(db) {
144 SyntaxNodeId::Child { parent, .. } => Some(*parent),
145 SyntaxNodeId::Root(_) => None,
146 }
147 }
148
149 pub fn grandparent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
152 self.parent?.parent(db)
153 }
154
155 pub fn is_root(self) -> bool {
157 self.parent.is_none()
158 }
159
160 pub fn stable_ptr(self, _db: &'db dyn Database) -> SyntaxStablePtrId<'db> {
162 SyntaxStablePtrId(self)
163 }
164
165 pub fn raw_id(&self, db: &'db dyn Database) -> &'db SyntaxNodeId<'db> {
166 self.data.id(db)
167 }
168
169 pub fn key_fields(self, db: &'db dyn Database) -> &'db [GreenId<'db>] {
171 match self.data.id(db) {
172 SyntaxNodeId::Child { key_fields, .. } => key_fields,
173 SyntaxNodeId::Root(_) => &[],
174 }
175 }
176
177 pub fn file_id(&self, db: &'db dyn Database) -> FileId<'db> {
179 if let Some(parent_data) = self.parent {
181 return match parent_data.id(db) {
182 SyntaxNodeId::Child { parent, .. } => parent.file_id(db),
183 SyntaxNodeId::Root(file_id) => *file_id,
184 };
185 }
186 match self.data.id(db) {
187 SyntaxNodeId::Root(file_id) => *file_id,
188 SyntaxNodeId::Child { .. } => {
189 unreachable!("Parent already checked and found to not exist.")
190 }
191 }
192 }
193
194 pub fn nth_parent<'r: 'db>(&self, db: &'r dyn Database, mut n: usize) -> SyntaxNode<'db> {
201 let mut result = Some(*self);
202 while let Some(p) = result
203 && n > 1
204 {
205 result = p.grandparent(db);
206 n -= 2;
207 }
208 if let Some(p) = result
209 && n == 1
210 {
211 result = p.parent(db);
212 }
213 result.unwrap_or_else(|| {
214 panic!(
215 "N'th parent did not exist. File {} Offset {:?}",
216 self.file_id(db).long(db).full_path(db),
217 self.offset(db)
218 )
219 })
220 }
221}
222
223pub fn new_syntax_node<'db>(
226 db: &'db dyn Database,
227 green: GreenId<'db>,
228 offset_in_parent: TextOffset,
229 id: SyntaxNodeId<'db>,
230 kind: SyntaxKind,
231) -> SyntaxNode<'db> {
232 let (parent, parent_kind) = match &id {
233 SyntaxNodeId::Child { parent, .. } => (Some(parent.data), Some(parent.kind)),
234 SyntaxNodeId::Root(_) => (None, None),
235 };
236 let data = SyntaxNodeData::new(db, green, offset_in_parent, id);
237 SyntaxNode { data, parent, kind, parent_kind }
238}
239
240#[salsa::tracked]
247fn new_detached_root_node<'db>(
248 db: &'db dyn Database,
249 file_id: FileId<'db>,
250 green: GreenId<'db>,
251 offset: TextOffset,
252) -> SyntaxNode<'db> {
253 let kind = green.long(db).kind;
254 new_syntax_node(db, green, offset, SyntaxNodeId::Root(file_id), kind)
255}
256
257impl<'a> SyntaxNode<'a> {
259 pub fn new_canonical_root(
266 db: &'a dyn Database,
267 file_id: FileId<'a>,
268 green: GreenId<'a>,
269 ) -> Self {
270 let kind = green.long(db).kind;
271 new_syntax_node(db, green, TextOffset::START, SyntaxNodeId::Root(file_id), kind)
272 }
273
274 pub fn new_detached_root(
280 db: &'a dyn Database,
281 file_id: FileId<'a>,
282 green: GreenId<'a>,
283 ) -> Self {
284 new_detached_root_node(db, file_id, green, TextOffset::START)
285 }
286
287 pub fn new_detached_root_with_offset(
290 db: &'a dyn Database,
291 file_id: FileId<'a>,
292 green: GreenId<'a>,
293 initial_offset: Option<TextOffset>,
294 ) -> Self {
295 new_detached_root_node(db, file_id, green, initial_offset.unwrap_or_default())
296 }
297
298 pub fn width(&self, db: &dyn Database) -> TextWidth {
302 self.green_node(db).width(db)
303 }
304
305 pub fn kind(&self, _db: &dyn Database) -> SyntaxKind {
307 self.kind
308 }
309
310 pub fn span(&self, db: &dyn Database) -> TextSpan {
312 TextSpan::new_with_width(self.offset(db), self.width(db))
313 }
314
315 pub fn text(&self, db: &'a dyn Database) -> Option<SmolStrId<'a>> {
317 match &self.green_node(db).details {
318 green::GreenNodeDetails::Token(text) => Some(*text),
319 green::GreenNodeDetails::Node { .. } => None,
320 }
321 }
322
323 pub fn green_node(&self, db: &'a dyn Database) -> &'a GreenNode<'a> {
325 self.data.green(db).long(db)
326 }
327
328 pub fn span_without_trivia(&self, db: &dyn Database) -> TextSpan {
330 let green_node = self.green_node(db);
331 let (leading, trailing) = both_trivia_width(db, green_node);
332 let offset = self.offset(db);
333 let start = offset.add_width(leading);
334 let end = offset.add_width(green_node.width(db)).sub_width(trailing);
335 TextSpan::new(start, end)
336 }
337
338 pub fn get_terminal_token(&'a self, db: &'a dyn Database) -> Option<SyntaxNode<'a>> {
341 let green_node = self.green_node(db);
342 require(green_node.kind.is_terminal())?;
343 self.get_children(db).get(1).copied()
345 }
346
347 pub fn get_children(&self, db: &'a dyn Database) -> &'a [SyntaxNode<'a>] {
351 db.get_children(*self)
352 }
353
354 pub(crate) fn get_children_impl(&self, db: &'a dyn Database) -> Vec<SyntaxNode<'a>> {
356 let mut offset = TextOffset::START;
359 let self_green = self.green_node(db);
360 let children = self_green.children();
361 let mut res: Vec<SyntaxNode<'_>> = Vec::with_capacity(children.len());
362 let mut key_map = Vec::<(_, usize)>::with_capacity(children.len());
363 for green_id in children {
364 let green = green_id.long(db);
365 let width = green.width(db);
366 let kind = green.kind;
367 let rng = key_fields::key_fields_range(kind);
368 let key_fields: &'a [GreenId<'a>] = &green.children()[rng];
369 let index = key_map
370 .iter_mut()
371 .find(|(k, _v)| *k == (kind, key_fields))
372 .map(|(_k, v)| {
373 let index = *v;
374 *v += 1;
375 index
376 })
377 .unwrap_or_else(|| {
378 key_map.push(((kind, key_fields), 1));
379 0
380 });
381 res.push(new_syntax_node(
383 db,
384 *green_id,
385 offset,
386 SyntaxNodeId::Child {
387 parent: *self,
388 kind,
389 index,
390 key_fields: Box::from(key_fields),
391 },
392 kind,
393 ));
394
395 offset = offset.add_width(width);
396 }
397 res
398 }
399
400 pub fn span_start_without_trivia(&self, db: &dyn Database) -> TextOffset {
404 let green_node = self.green_node(db);
405 let leading = leading_trivia_width(db, green_node);
406 self.offset(db).add_width(leading)
407 }
408
409 pub fn span_end_without_trivia(&self, db: &dyn Database) -> TextOffset {
411 let green_node = self.green_node(db);
412 let trailing = trailing_trivia_width(db, green_node);
413 self.offset(db).add_width(green_node.width(db)).sub_width(trailing)
414 }
415
416 pub fn lookup_offset(&self, db: &'a dyn Database, offset: TextOffset) -> SyntaxNode<'a> {
418 for child in self.get_children(db).iter() {
419 if child.offset(db).add_width(child.width(db)) > offset {
420 return child.lookup_offset(db, offset);
421 }
422 }
423 *self
424 }
425
426 pub fn lookup_position(&self, db: &'a dyn Database, position: TextPosition) -> SyntaxNode<'a> {
428 match position.offset_in_file(db, self.stable_ptr(db).file_id(db)) {
429 Some(offset) => self.lookup_offset(db, offset),
430 None => *self,
431 }
432 }
433
434 pub fn get_text(&self, db: &'a dyn Database) -> &'a str {
436 let file_content =
440 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
441
442 self.span(db).take(file_content)
443 }
444
445 pub fn get_text_without_inner_commentable_children(&self, db: &dyn Database) -> String {
450 let mut buffer = String::new();
451
452 match &self.green_node(db).details {
453 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
454 green::GreenNodeDetails::Node { .. } => {
455 for child in self.get_children(db).iter() {
456 let kind = child.kind(db);
457
458 if !matches!(
461 kind,
462 SyntaxKind::FunctionWithBody
463 | SyntaxKind::ItemModule
464 | SyntaxKind::TraitItemFunction
465 ) {
466 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
467 child, db,
468 ));
469 }
470 }
471 }
472 }
473 buffer
474 }
475
476 pub fn get_text_without_all_comment_trivia(&self, db: &dyn Database) -> String {
479 let mut buffer = String::new();
480
481 match &self.green_node(db).details {
482 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
483 green::GreenNodeDetails::Node { .. } => {
484 for child in self.get_children(db).iter() {
485 if let Some(trivia) = ast::Trivia::cast(db, *child) {
486 trivia.elements(db).for_each(|element| {
487 if !matches!(
488 element,
489 ast::Trivium::SingleLineComment(_)
490 | ast::Trivium::SingleLineDocComment(_)
491 | ast::Trivium::SingleLineInnerComment(_)
492 ) {
493 buffer.push_str(
494 &element
495 .as_syntax_node()
496 .get_text_without_all_comment_trivia(db),
497 );
498 }
499 });
500 } else {
501 buffer
502 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
503 }
504 }
505 }
506 }
507 buffer
508 }
509
510 pub fn get_text_without_trivia(self, db: &'a dyn Database) -> SmolStrId<'a> {
515 let file_content =
516 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
517 SmolStrId::from(db, self.span_without_trivia(db).take(file_content))
518 }
519
520 pub fn get_text_of_span(self, db: &'a dyn Database, span: TextSpan) -> &'a str {
526 assert!(self.span(db).contains(span));
527 let file_content =
528 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
529 span.take(file_content)
530 }
531
532 pub fn descendants(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
539 self.preorder(db).filter_map(|event| match event {
540 WalkEvent::Enter(node) => Some(node),
541 WalkEvent::Leave(_) => None,
542 })
543 }
544
545 pub fn preorder(&self, db: &'a dyn Database) -> Preorder<'a> {
548 Preorder::new(*self, db)
549 }
550
551 pub fn tokens(&self, db: &'a dyn Database) -> impl Iterator<Item = Self> + 'a {
553 self.preorder(db).filter_map(|event| match event {
554 WalkEvent::Enter(node) if node.kind(db).is_terminal() => Some(node),
555 _ => None,
556 })
557 }
558
559 pub fn cast<T: TypedSyntaxNode<'a>>(self, db: &'a dyn Database) -> Option<T> {
561 T::cast(db, self)
562 }
563
564 pub fn ancestors(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
568 std::iter::successors(self.parent(db), |n| n.parent(db))
570 }
571
572 pub fn ancestors_with_self(
574 &self,
575 db: &'a dyn Database,
576 ) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
577 std::iter::successors(Some(*self), |n| n.parent(db))
578 }
579
580 pub fn is_ancestor(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
582 node.ancestors(db).any(|n| n == *self)
583 }
584
585 pub fn is_descendant(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
587 node.is_ancestor(db, self)
588 }
589
590 pub fn is_ancestor_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
592 node.ancestors_with_self(db).any(|n| n == *self)
593 }
594
595 pub fn is_descendant_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
597 node.is_ancestor_or_self(db, self)
598 }
599
600 pub fn ancestor_of_kind(
604 &self,
605 db: &'a dyn Database,
606 kind: SyntaxKind,
607 ) -> Option<SyntaxNode<'a>> {
608 self.ancestors(db).find(|node| node.kind(db) == kind)
609 }
610
611 pub fn ancestor_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
613 self.ancestors(db).find_map(|node| T::cast(db, node))
614 }
615
616 pub fn parent_of_kind(&self, db: &'a dyn Database, kind: SyntaxKind) -> Option<SyntaxNode<'a>> {
618 self.parent(db).filter(|node| node.kind(db) == kind)
619 }
620
621 pub fn parent_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
623 self.parent(db).and_then(|node| T::cast(db, node))
624 }
625
626 pub fn ancestor_of_kinds(
628 &self,
629 db: &'a dyn Database,
630 kinds: &[SyntaxKind],
631 ) -> Option<SyntaxNode<'a>> {
632 self.ancestors(db).find(|node| kinds.contains(&node.kind(db)))
633 }
634
635 pub fn parent_kind(&self, _db: &dyn Database) -> Option<SyntaxKind> {
637 self.parent_kind
638 }
639
640 pub fn grandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
642 self.parent(db)?.parent_kind(db)
643 }
644
645 pub fn grandgrandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
647 self.grandparent(db)?.parent_kind(db)
648 }
649}
650
651pub trait TypedSyntaxNode<'a>: Sized {
654 const OPTIONAL_KIND: Option<SyntaxKind>;
656 type StablePtr: TypedStablePtr<'a>;
657 type Green;
658 fn missing(db: &'a dyn Database) -> Self::Green;
659 fn from_syntax_node(db: &'a dyn Database, node: SyntaxNode<'a>) -> Self;
660 fn cast(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self>;
661 fn as_syntax_node(&self) -> SyntaxNode<'a>;
662 fn stable_ptr(&self, db: &'a dyn Database) -> Self::StablePtr;
663}
664
665pub trait Token<'a>: TypedSyntaxNode<'a> {
666 fn new_green(db: &'a dyn Database, text: SmolStrId<'a>) -> Self::Green;
667 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
668}
669
670pub trait Terminal<'a>: TypedSyntaxNode<'a> {
671 const KIND: SyntaxKind;
672 type TokenType: Token<'a>;
673 fn new_green(
674 db: &'a dyn Database,
675 leading_trivia: TriviaGreen<'a>,
676 token: <<Self as Terminal<'a>>::TokenType as TypedSyntaxNode<'a>>::Green,
677 trailing_trivia: TriviaGreen<'a>,
678 ) -> <Self as TypedSyntaxNode<'a>>::Green;
679 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
681 fn cast_token(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self> {
683 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
684 Some(Self::from_syntax_node(db, node.parent(db)?))
685 } else {
686 None
687 }
688 }
689}
690
691pub trait TypedStablePtr<'a> {
693 type SyntaxNode: TypedSyntaxNode<'a>;
694 fn lookup(&self, db: &'a dyn Database) -> Self::SyntaxNode;
696 fn untyped(self) -> SyntaxStablePtrId<'a>;
698}
699
700fn leading_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
702 match &green.details {
703 green::GreenNodeDetails::Token(_) => TextWidth::default(),
704 green::GreenNodeDetails::Node { children, width } => {
705 if *width == TextWidth::default() {
706 return TextWidth::default();
707 }
708 if green.kind.is_terminal() {
709 return children[0].long(db).width(db);
710 }
711 let non_empty = find_non_empty_child(db, &mut children.iter())
712 .expect("Parent width non-empty - one of the children should be non-empty");
713 leading_trivia_width(db, non_empty)
714 }
715 }
716}
717
718fn trailing_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
720 match &green.details {
721 green::GreenNodeDetails::Token(_) => TextWidth::default(),
722 green::GreenNodeDetails::Node { children, width } => {
723 if *width == TextWidth::default() {
724 return TextWidth::default();
725 }
726 if green.kind.is_terminal() {
727 return children[2].long(db).width(db);
728 }
729 let non_empty = find_non_empty_child(db, &mut children.iter().rev())
730 .expect("Parent width non-empty - one of the children should be non-empty");
731 trailing_trivia_width(db, non_empty)
732 }
733 }
734}
735
736fn both_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> (TextWidth, TextWidth) {
738 match &green.details {
739 green::GreenNodeDetails::Token(_) => (TextWidth::default(), TextWidth::default()),
740 green::GreenNodeDetails::Node { children, width } => {
741 if *width == TextWidth::default() {
742 return (TextWidth::default(), TextWidth::default());
743 }
744 if green.kind.is_terminal() {
745 return (children[0].long(db).width(db), children[2].long(db).width(db));
746 }
747 let mut iter = children.iter();
748 let first_non_empty = find_non_empty_child(db, &mut iter)
749 .expect("Parent width non-empty - one of the children should be non-empty");
750 if let Some(last_non_empty) = find_non_empty_child(db, &mut iter.rev()) {
751 (
752 leading_trivia_width(db, first_non_empty),
753 trailing_trivia_width(db, last_non_empty),
754 )
755 } else {
756 both_trivia_width(db, first_non_empty)
757 }
758 }
759 }
760}
761
762fn find_non_empty_child<'a>(
764 db: &'a dyn Database,
765 child_iter: &mut impl Iterator<Item = &'a GreenId<'a>>,
766) -> Option<&'a GreenNode<'a>> {
767 child_iter.find_map(|child| {
768 let child = child.long(db);
769 (child.width(db) != TextWidth::default()).then_some(child)
770 })
771}