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;
7use cairo_lang_utils::require;
8use salsa::Database;
9use salsa::plumbing::AsId;
10use vector_map::VecMap;
11
12use self::ast::TriviaGreen;
13use self::green::GreenNode;
14use self::ids::{GreenId, SyntaxStablePtrId};
15use self::kind::SyntaxKind;
16use crate::node::db::SyntaxGroup;
17use crate::node::iter::{Preorder, WalkEvent};
18
19pub mod ast;
20pub mod db;
21pub mod element_list;
22pub mod green;
23pub mod helpers;
24pub mod ids;
25pub mod iter;
26pub mod key_fields;
27pub mod kind;
28pub mod stable_ptr;
29pub mod with_db;
30
31#[cfg(test)]
32mod ast_test;
33#[cfg(test)]
34mod test_utils;
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash, DebugWithDb, salsa::Update)]
38#[debug_db(dyn Database)]
39pub enum SyntaxNodeId<'db> {
40 Root(FileId<'db>),
41 Child {
42 parent: SyntaxNode<'db>,
44 index: usize,
46 key_fields: Box<[GreenId<'db>]>,
49 },
50}
51
52#[salsa::tracked]
55#[derive(Debug)]
56struct SyntaxNodeData<'a> {
57 #[tracked]
58 green: GreenId<'a>,
59 #[tracked]
62 offset: TextOffset,
63 #[returns(ref)]
65 id: SyntaxNodeId<'a>,
66}
67
68impl<'db> SyntaxNodeData<'db> {
69 pub fn parent(&self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
71 match self.id(db) {
72 SyntaxNodeId::Root(_) => None,
73 SyntaxNodeId::Child { parent, .. } => Some(*parent),
74 }
75 }
76}
77
78impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNodeData<'db> {
79 type Db = dyn Database;
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
81 f.debug_struct("SyntaxNode")
82 .field("green", &self.green(db).debug(db))
83 .field("offset", &self.offset(db))
84 .field("id", &self.id(db).debug(db))
85 .finish()
86 }
87}
88
89#[derive(Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
95pub struct SyntaxNode<'a> {
96 data: SyntaxNodeData<'a>,
97 parent: Option<SyntaxNodeData<'a>>,
99 kind: SyntaxKind,
101 parent_kind: Option<SyntaxKind>,
103}
104
105impl<'db> std::fmt::Debug for SyntaxNode<'db> {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 write!(f, "SyntaxNode({:x})", self.data.as_id().index())
108 }
109}
110
111impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNode<'db> {
112 type Db = dyn Database;
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
114 self.data.fmt(f, db)
115 }
116}
117
118impl<'db> SyntaxNode<'db> {
119 pub fn offset(self, db: &'db dyn Database) -> TextOffset {
121 self.data.offset(db)
122 }
123
124 pub fn parent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
126 match self.data.id(db) {
127 SyntaxNodeId::Child { parent, .. } => Some(*parent),
128 SyntaxNodeId::Root(_) => None,
129 }
130 }
131
132 pub fn grandparent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
135 self.parent?.parent(db)
136 }
137
138 pub fn is_root(self) -> bool {
140 self.parent.is_none()
141 }
142
143 pub fn stable_ptr(self, _db: &'db dyn Database) -> SyntaxStablePtrId<'db> {
145 SyntaxStablePtrId(self)
146 }
147
148 pub fn raw_id(&self, db: &'db dyn Database) -> &'db SyntaxNodeId<'db> {
149 self.data.id(db)
150 }
151
152 pub fn key_fields(self, db: &'db dyn Database) -> &'db [GreenId<'db>] {
154 match self.data.id(db) {
155 SyntaxNodeId::Child { key_fields, .. } => key_fields,
156 SyntaxNodeId::Root(_) => &[],
157 }
158 }
159
160 pub fn file_id(&self, db: &'db dyn Database) -> FileId<'db> {
162 if let Some(parent_data) = self.parent {
164 return match parent_data.id(db) {
165 SyntaxNodeId::Child { parent, .. } => parent.file_id(db),
166 SyntaxNodeId::Root(file_id) => *file_id,
167 };
168 }
169 match self.data.id(db) {
170 SyntaxNodeId::Root(file_id) => *file_id,
171 SyntaxNodeId::Child { .. } => {
172 unreachable!("Parent already checked and found to not exist.")
173 }
174 }
175 }
176
177 pub fn nth_parent<'r: 'db>(&self, db: &'r dyn Database, mut n: usize) -> SyntaxNode<'db> {
184 let mut result = Some(*self);
185 while let Some(p) = result
186 && n > 1
187 {
188 result = p.grandparent(db);
189 n -= 2;
190 }
191 if let Some(p) = result
192 && n == 1
193 {
194 result = p.parent(db);
195 }
196 result.unwrap_or_else(|| {
197 panic!(
198 "N'th parent did not exist. File {} Offset {:?}",
199 self.file_id(db).long(db).full_path(db),
200 self.offset(db)
201 )
202 })
203 }
204}
205
206pub fn new_syntax_node<'db>(
208 db: &'db dyn Database,
209 green: GreenId<'db>,
210 offset: TextOffset,
211 id: SyntaxNodeId<'db>,
212 kind: SyntaxKind,
213) -> SyntaxNode<'db> {
214 let (parent, parent_kind) = match &id {
215 SyntaxNodeId::Child { parent, .. } => (Some(parent.data), Some(parent.kind)),
216 SyntaxNodeId::Root(_) => (None, None),
217 };
218 let data = SyntaxNodeData::new(db, green, offset, id);
219 SyntaxNode { data, parent, kind, parent_kind }
220}
221
222#[salsa::tracked]
224fn new_root_node<'db>(
225 db: &'db dyn Database,
226 file_id: FileId<'db>,
227 green: GreenId<'db>,
228 offset: TextOffset,
229) -> SyntaxNode<'db> {
230 let kind = green.long(db).kind;
231 new_syntax_node(db, green, offset, SyntaxNodeId::Root(file_id), kind)
232}
233
234impl<'a> SyntaxNode<'a> {
236 pub fn new_root(db: &'a dyn Database, file_id: FileId<'a>, green: GreenId<'a>) -> Self {
238 new_root_node(db, file_id, green, TextOffset::START)
239 }
240
241 pub fn new_root_with_offset(
243 db: &'a dyn Database,
244 file_id: FileId<'a>,
245 green: GreenId<'a>,
246 initial_offset: Option<TextOffset>,
247 ) -> Self {
248 new_root_node(db, file_id, green, initial_offset.unwrap_or_default())
249 }
250
251 pub fn width(&self, db: &dyn Database) -> TextWidth {
255 self.green_node(db).width(db)
256 }
257
258 pub fn kind(&self, _db: &dyn Database) -> SyntaxKind {
260 self.kind
261 }
262
263 pub fn span(&self, db: &dyn Database) -> TextSpan {
265 TextSpan::new_with_width(self.offset(db), self.width(db))
266 }
267
268 pub fn text(&self, db: &'a dyn Database) -> Option<SmolStrId<'a>> {
270 match &self.green_node(db).details {
271 green::GreenNodeDetails::Token(text) => Some(*text),
272 green::GreenNodeDetails::Node { .. } => None,
273 }
274 }
275
276 pub fn green_node(&self, db: &'a dyn Database) -> &'a GreenNode<'a> {
278 self.data.green(db).long(db)
279 }
280
281 pub fn span_without_trivia(&self, db: &dyn Database) -> TextSpan {
283 let green_node = self.green_node(db);
284 let (leading, trailing) = both_trivia_width(db, green_node);
285 let offset = self.offset(db);
286 let start = offset.add_width(leading);
287 let end = offset.add_width(green_node.width(db)).sub_width(trailing);
288 TextSpan::new(start, end)
289 }
290
291 pub fn get_terminal_token(&'a self, db: &'a dyn Database) -> Option<SyntaxNode<'a>> {
294 let green_node = self.green_node(db);
295 require(green_node.kind.is_terminal())?;
296 self.get_children(db).get(1).copied()
298 }
299
300 pub fn get_children(&self, db: &'a dyn Database) -> &'a [SyntaxNode<'a>] {
304 db.get_children(*self)
305 }
306
307 pub(crate) fn get_children_impl(&self, db: &'a dyn Database) -> Vec<SyntaxNode<'a>> {
309 let mut offset = self.offset(db);
310 let self_green = self.green_node(db);
311 let children = self_green.children();
312 let mut res: Vec<SyntaxNode<'_>> = Vec::with_capacity(children.len());
313 let mut key_map = VecMap::<_, usize>::new();
314 for green_id in children {
315 let green = green_id.long(db);
316 let width = green.width(db);
317 let kind = green.kind;
318 let rng = key_fields::key_fields_range(kind);
319 let key_fields: &'a [GreenId<'a>] = &green.children()[rng];
320 let key_count = key_map.entry((kind, key_fields)).or_insert(0);
321 let index = *key_count;
322 *key_count += 1;
323 res.push(new_syntax_node(
325 db,
326 *green_id,
327 offset,
328 SyntaxNodeId::Child { parent: *self, index, key_fields: Box::from(key_fields) },
329 kind,
330 ));
331
332 offset = offset.add_width(width);
333 }
334 res
335 }
336
337 pub fn span_start_without_trivia(&self, db: &dyn Database) -> TextOffset {
341 let green_node = self.green_node(db);
342 let leading = leading_trivia_width(db, green_node);
343 self.offset(db).add_width(leading)
344 }
345
346 pub fn span_end_without_trivia(&self, db: &dyn Database) -> TextOffset {
348 let green_node = self.green_node(db);
349 let trailing = trailing_trivia_width(db, green_node);
350 self.offset(db).add_width(green_node.width(db)).sub_width(trailing)
351 }
352
353 pub fn lookup_offset(&self, db: &'a dyn Database, offset: TextOffset) -> SyntaxNode<'a> {
355 for child in self.get_children(db).iter() {
356 if child.offset(db).add_width(child.width(db)) > offset {
357 return child.lookup_offset(db, offset);
358 }
359 }
360 *self
361 }
362
363 pub fn lookup_position(&self, db: &'a dyn Database, position: TextPosition) -> SyntaxNode<'a> {
365 match position.offset_in_file(db, self.stable_ptr(db).file_id(db)) {
366 Some(offset) => self.lookup_offset(db, offset),
367 None => *self,
368 }
369 }
370
371 pub fn get_text(&self, db: &'a dyn Database) -> &'a str {
373 let file_content =
377 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
378
379 self.span(db).take(file_content)
380 }
381
382 pub fn get_text_without_inner_commentable_children(&self, db: &dyn Database) -> String {
387 let mut buffer = String::new();
388
389 match &self.green_node(db).details {
390 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
391 green::GreenNodeDetails::Node { .. } => {
392 for child in self.get_children(db).iter() {
393 let kind = child.kind(db);
394
395 if !matches!(
398 kind,
399 SyntaxKind::FunctionWithBody
400 | SyntaxKind::ItemModule
401 | SyntaxKind::TraitItemFunction
402 ) {
403 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
404 child, db,
405 ));
406 }
407 }
408 }
409 }
410 buffer
411 }
412
413 pub fn get_text_without_all_comment_trivia(&self, db: &dyn Database) -> String {
416 let mut buffer = String::new();
417
418 match &self.green_node(db).details {
419 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
420 green::GreenNodeDetails::Node { .. } => {
421 for child in self.get_children(db).iter() {
422 if let Some(trivia) = ast::Trivia::cast(db, *child) {
423 trivia.elements(db).for_each(|element| {
424 if !matches!(
425 element,
426 ast::Trivium::SingleLineComment(_)
427 | ast::Trivium::SingleLineDocComment(_)
428 | ast::Trivium::SingleLineInnerComment(_)
429 ) {
430 buffer.push_str(
431 &element
432 .as_syntax_node()
433 .get_text_without_all_comment_trivia(db),
434 );
435 }
436 });
437 } else {
438 buffer
439 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
440 }
441 }
442 }
443 }
444 buffer
445 }
446
447 pub fn get_text_without_trivia(self, db: &'a dyn Database) -> SmolStrId<'a> {
452 let file_content =
453 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
454 SmolStrId::from(db, self.span_without_trivia(db).take(file_content))
455 }
456
457 pub fn get_text_of_span(self, db: &'a dyn Database, span: TextSpan) -> &'a str {
463 assert!(self.span(db).contains(span));
464 let file_content =
465 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
466 span.take(file_content)
467 }
468
469 pub fn descendants(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
476 self.preorder(db).filter_map(|event| match event {
477 WalkEvent::Enter(node) => Some(node),
478 WalkEvent::Leave(_) => None,
479 })
480 }
481
482 pub fn preorder(&self, db: &'a dyn Database) -> Preorder<'a> {
485 Preorder::new(*self, db)
486 }
487
488 pub fn tokens(&self, db: &'a dyn Database) -> impl Iterator<Item = Self> + 'a {
490 self.preorder(db).filter_map(|event| match event {
491 WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
492 _ => None,
493 })
494 }
495
496 pub fn cast<T: TypedSyntaxNode<'a>>(self, db: &'a dyn Database) -> Option<T> {
498 T::cast(db, self)
499 }
500
501 pub fn ancestors(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
505 std::iter::successors(self.parent(db), |n| n.parent(db))
507 }
508
509 pub fn ancestors_with_self(
511 &self,
512 db: &'a dyn Database,
513 ) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
514 std::iter::successors(Some(*self), |n| n.parent(db))
515 }
516
517 pub fn is_ancestor(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
519 node.ancestors(db).any(|n| n == *self)
520 }
521
522 pub fn is_descendant(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
524 node.is_ancestor(db, self)
525 }
526
527 pub fn is_ancestor_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
529 node.ancestors_with_self(db).any(|n| n == *self)
530 }
531
532 pub fn is_descendant_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
534 node.is_ancestor_or_self(db, self)
535 }
536
537 pub fn ancestor_of_kind(
541 &self,
542 db: &'a dyn Database,
543 kind: SyntaxKind,
544 ) -> Option<SyntaxNode<'a>> {
545 self.ancestors(db).find(|node| node.kind(db) == kind)
546 }
547
548 pub fn ancestor_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
550 self.ancestors(db).find_map(|node| T::cast(db, node))
551 }
552
553 pub fn parent_of_kind(&self, db: &'a dyn Database, kind: SyntaxKind) -> Option<SyntaxNode<'a>> {
555 self.parent(db).filter(|node| node.kind(db) == kind)
556 }
557
558 pub fn parent_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
560 self.parent(db).and_then(|node| T::cast(db, node))
561 }
562
563 pub fn ancestor_of_kinds(
565 &self,
566 db: &'a dyn Database,
567 kinds: &[SyntaxKind],
568 ) -> Option<SyntaxNode<'a>> {
569 self.ancestors(db).find(|node| kinds.contains(&node.kind(db)))
570 }
571
572 pub fn parent_kind(&self, _db: &dyn Database) -> Option<SyntaxKind> {
574 self.parent_kind
575 }
576
577 pub fn grandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
579 self.parent(db)?.parent_kind(db)
580 }
581
582 pub fn grandgrandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
584 self.grandparent(db)?.parent_kind(db)
585 }
586}
587
588pub trait TypedSyntaxNode<'a>: Sized {
591 const OPTIONAL_KIND: Option<SyntaxKind>;
593 type StablePtr: TypedStablePtr<'a>;
594 type Green;
595 fn missing(db: &'a dyn Database) -> Self::Green;
596 fn from_syntax_node(db: &'a dyn Database, node: SyntaxNode<'a>) -> Self;
597 fn cast(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self>;
598 fn as_syntax_node(&self) -> SyntaxNode<'a>;
599 fn stable_ptr(&self, db: &'a dyn Database) -> Self::StablePtr;
600}
601
602pub trait Token<'a>: TypedSyntaxNode<'a> {
603 fn new_green(db: &'a dyn Database, text: SmolStrId<'a>) -> Self::Green;
604 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
605}
606
607pub trait Terminal<'a>: TypedSyntaxNode<'a> {
608 const KIND: SyntaxKind;
609 type TokenType: Token<'a>;
610 fn new_green(
611 db: &'a dyn Database,
612 leading_trivia: TriviaGreen<'a>,
613 token: <<Self as Terminal<'a>>::TokenType as TypedSyntaxNode<'a>>::Green,
614 trailing_trivia: TriviaGreen<'a>,
615 ) -> <Self as TypedSyntaxNode<'a>>::Green;
616 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
618 fn cast_token(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self> {
620 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
621 Some(Self::from_syntax_node(db, node.parent(db)?))
622 } else {
623 None
624 }
625 }
626}
627
628pub trait TypedStablePtr<'a> {
630 type SyntaxNode: TypedSyntaxNode<'a>;
631 fn lookup(&self, db: &'a dyn Database) -> Self::SyntaxNode;
633 fn untyped(self) -> SyntaxStablePtrId<'a>;
635}
636
637fn leading_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
639 match &green.details {
640 green::GreenNodeDetails::Token(_) => TextWidth::default(),
641 green::GreenNodeDetails::Node { children, width } => {
642 if *width == TextWidth::default() {
643 return TextWidth::default();
644 }
645 if green.kind.is_terminal() {
646 return children[0].long(db).width(db);
647 }
648 let non_empty = find_non_empty_child(db, &mut children.iter())
649 .expect("Parent width non-empty - one of the children should be non-empty");
650 leading_trivia_width(db, non_empty)
651 }
652 }
653}
654
655fn trailing_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
657 match &green.details {
658 green::GreenNodeDetails::Token(_) => TextWidth::default(),
659 green::GreenNodeDetails::Node { children, width } => {
660 if *width == TextWidth::default() {
661 return TextWidth::default();
662 }
663 if green.kind.is_terminal() {
664 return children[2].long(db).width(db);
665 }
666 let non_empty = find_non_empty_child(db, &mut children.iter().rev())
667 .expect("Parent width non-empty - one of the children should be non-empty");
668 trailing_trivia_width(db, non_empty)
669 }
670 }
671}
672
673fn both_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> (TextWidth, TextWidth) {
675 match &green.details {
676 green::GreenNodeDetails::Token(_) => (TextWidth::default(), TextWidth::default()),
677 green::GreenNodeDetails::Node { children, width } => {
678 if *width == TextWidth::default() {
679 return (TextWidth::default(), TextWidth::default());
680 }
681 if green.kind.is_terminal() {
682 return (children[0].long(db).width(db), children[2].long(db).width(db));
683 }
684 let mut iter = children.iter();
685 let first_non_empty = find_non_empty_child(db, &mut iter)
686 .expect("Parent width non-empty - one of the children should be non-empty");
687 if let Some(last_non_empty) = find_non_empty_child(db, &mut iter.rev()) {
688 (
689 leading_trivia_width(db, first_non_empty),
690 trailing_trivia_width(db, last_non_empty),
691 )
692 } else {
693 both_trivia_width(db, first_non_empty)
694 }
695 }
696 }
697}
698
699fn find_non_empty_child<'a>(
701 db: &'a dyn Database,
702 child_iter: &mut impl Iterator<Item = &'a GreenId<'a>>,
703) -> Option<&'a GreenNode<'a>> {
704 child_iter.find_map(|child| {
705 let child = child.long(db);
706 (child.width(db) != TextWidth::default()).then_some(child)
707 })
708}