1use core::hash::Hash;
2use std::fmt::Display;
3use std::sync::Arc;
4
5use cairo_lang_filesystem::ids::FileId;
6use cairo_lang_filesystem::span::{TextOffset, TextPosition, TextSpan, TextWidth};
7use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
8use cairo_lang_utils::{Intern, LookupIntern, define_short_id, require};
9use key_fields::get_key_fields;
10use smol_str::SmolStr;
11
12use self::ast::TriviaGreen;
13use self::db::SyntaxGroup;
14use self::green::GreenNode;
15use self::ids::{GreenId, SyntaxStablePtrId};
16use self::kind::SyntaxKind;
17use self::stable_ptr::SyntaxStablePtr;
18use crate::node::iter::{Preorder, WalkEvent};
19
20pub mod ast;
21pub mod db;
22pub mod element_list;
23pub mod green;
24pub mod helpers;
25pub mod ids;
26pub mod iter;
27pub mod key_fields;
28pub mod kind;
29pub mod stable_ptr;
30pub mod with_db;
31
32#[cfg(test)]
33mod ast_test;
34#[cfg(test)]
35mod test_utils;
36
37#[derive(Clone, Debug, Hash, PartialEq, Eq)]
40pub struct SyntaxNodeLongId {
41 green: GreenId,
42 offset: TextOffset,
45 parent: Option<SyntaxNode>,
46 stable_ptr: SyntaxStablePtrId,
47}
48define_short_id!(
49 SyntaxNode,
50 SyntaxNodeLongId,
51 SyntaxGroup,
52 lookup_intern_syntax_node,
53 intern_syntax_node
54);
55impl SyntaxNode {
56 pub fn new_root(db: &dyn SyntaxGroup, file_id: FileId, green: GreenId) -> Self {
57 Self::new_with_inner(
58 db,
59 green,
60 TextOffset::START,
61 None,
62 SyntaxStablePtr::Root(file_id, green).intern(db),
63 )
64 }
65
66 pub fn new_root_with_offset(
67 db: &dyn SyntaxGroup,
68 file_id: FileId,
69 green: GreenId,
70 initial_offset: Option<TextOffset>,
71 ) -> Self {
72 Self::new_with_inner(
73 db,
74 green,
75 initial_offset.unwrap_or_default(),
76 None,
77 SyntaxStablePtr::Root(file_id, green).intern(db),
78 )
79 }
80
81 pub fn new_with_inner(
82 db: &dyn SyntaxGroup,
83 green: GreenId,
84 offset: TextOffset,
85 parent: Option<SyntaxNode>,
86 stable_ptr: SyntaxStablePtrId,
87 ) -> Self {
88 SyntaxNodeLongId { green, offset, parent, stable_ptr }.intern(db)
89 }
90
91 pub fn offset(&self, db: &dyn SyntaxGroup) -> TextOffset {
92 self.lookup_intern(db).offset
93 }
94 pub fn width(&self, db: &dyn SyntaxGroup) -> TextWidth {
95 self.green_node(db).width()
96 }
97 pub fn kind(&self, db: &dyn SyntaxGroup) -> SyntaxKind {
98 self.green_node(db).kind
99 }
100 pub fn span(&self, db: &dyn SyntaxGroup) -> TextSpan {
101 let start = self.offset(db);
102 let end = start.add_width(self.width(db));
103 TextSpan { start, end }
104 }
105 pub fn text(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
107 match &self.green_node(db).details {
108 green::GreenNodeDetails::Token(text) => Some(text.clone()),
109 green::GreenNodeDetails::Node { .. } => None,
110 }
111 }
112 pub fn green_node(&self, db: &dyn SyntaxGroup) -> Arc<GreenNode> {
113 self.lookup_intern(db).green.lookup_intern(db)
114 }
115 pub fn span_without_trivia(&self, db: &dyn SyntaxGroup) -> TextSpan {
116 let start = self.span_start_without_trivia(db);
117 let end = self.span_end_without_trivia(db);
118 TextSpan { start, end }
119 }
120 pub fn parent(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
121 self.lookup_intern(db).parent.as_ref().cloned()
122 }
123 pub fn stable_ptr(&self, db: &dyn SyntaxGroup) -> SyntaxStablePtrId {
124 self.lookup_intern(db).stable_ptr
125 }
126
127 pub fn get_terminal_token(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
130 let green_node = self.green_node(db);
131 require(green_node.kind.is_terminal())?;
132 self.get_children(db).into_iter().nth(1)
134 }
135
136 pub fn get_children(&self, db: &dyn SyntaxGroup) -> Vec<SyntaxNode> {
137 let mut res: Vec<SyntaxNode> = Vec::new();
138
139 let mut offset = self.offset(db);
140 let mut key_map = UnorderedHashMap::<_, usize>::default();
141 for green_id in self.green_node(db).children() {
142 let green = green_id.lookup_intern(db);
143 let width = green.width();
144 let kind = green.kind;
145 let key_fields: Vec<GreenId> = get_key_fields(kind, green.children());
146 let key_count = key_map.entry((kind, key_fields.clone())).or_default();
147 let stable_ptr = SyntaxStablePtr::Child {
148 parent: self.stable_ptr(db),
149 kind,
150 key_fields,
151 index: *key_count,
152 }
153 .intern(db);
154 *key_count += 1;
155 res.push(
157 SyntaxNodeLongId { green: *green_id, offset, parent: Some(*self), stable_ptr }
158 .intern(db),
159 );
160
161 offset = offset.add_width(width);
162 }
163 res
164 }
165
166 pub fn span_start_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
167 let green_node = self.green_node(db);
168 match green_node.details {
169 green::GreenNodeDetails::Node { .. } => {
170 if let Some(token_node) = self.get_terminal_token(db) {
171 return token_node.offset(db);
172 }
173 let children = self.get_children(db);
174 if let Some(child) =
175 children.iter().find(|child| child.width(db) != TextWidth::default())
176 {
177 child.span_start_without_trivia(db)
178 } else {
179 self.offset(db)
180 }
181 }
182 green::GreenNodeDetails::Token(_) => self.offset(db),
183 }
184 }
185 pub fn span_end_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
186 let green_node = self.green_node(db);
187 match green_node.details {
188 green::GreenNodeDetails::Node { .. } => {
189 if let Some(token_node) = self.get_terminal_token(db) {
190 return token_node.span(db).end;
191 }
192 if let Some(child) = self
193 .get_children(db)
194 .into_iter()
195 .filter(|child| child.width(db) != TextWidth::default())
196 .next_back()
197 {
198 child.span_end_without_trivia(db)
199 } else {
200 self.span(db).end
201 }
202 }
203 green::GreenNodeDetails::Token(_) => self.span(db).end,
204 }
205 }
206
207 pub fn lookup_offset(&self, db: &dyn SyntaxGroup, offset: TextOffset) -> SyntaxNode {
209 for child in self.get_children(db) {
210 if child.offset(db).add_width(child.width(db)) > offset {
211 return child.lookup_offset(db, offset);
212 }
213 }
214 *self
215 }
216
217 pub fn lookup_position(&self, db: &dyn SyntaxGroup, position: TextPosition) -> SyntaxNode {
219 match position.offset_in_file(db, self.stable_ptr(db).file_id(db)) {
220 Some(offset) => self.lookup_offset(db, offset),
221 None => *self,
222 }
223 }
224
225 pub fn get_text(&self, db: &dyn SyntaxGroup) -> String {
228 format!("{}", NodeTextFormatter { node: self, db })
229 }
230
231 pub fn get_text_without_inner_commentable_children(&self, db: &dyn SyntaxGroup) -> String {
236 let mut buffer = String::new();
237
238 match &self.green_node(db).as_ref().details {
239 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
240 green::GreenNodeDetails::Node { .. } => {
241 for child in self.get_children(db) {
242 let kind = child.kind(db);
243
244 if !matches!(
247 kind,
248 SyntaxKind::FunctionWithBody
249 | SyntaxKind::ItemModule
250 | SyntaxKind::TraitItemFunction
251 ) {
252 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
253 &child, db,
254 ));
255 }
256 }
257 }
258 }
259 buffer
260 }
261
262 pub fn get_text_without_all_comment_trivia(&self, db: &dyn SyntaxGroup) -> String {
265 let mut buffer = String::new();
266
267 match &self.green_node(db).as_ref().details {
268 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
269 green::GreenNodeDetails::Node { .. } => {
270 for child in self.get_children(db) {
271 if let Some(trivia) = ast::Trivia::cast(db, child) {
272 trivia.elements(db).iter().for_each(|element| {
273 if !matches!(
274 element,
275 ast::Trivium::SingleLineComment(_)
276 | ast::Trivium::SingleLineDocComment(_)
277 | ast::Trivium::SingleLineInnerComment(_)
278 ) {
279 buffer.push_str(
280 &element
281 .as_syntax_node()
282 .get_text_without_all_comment_trivia(db),
283 );
284 }
285 });
286 } else {
287 buffer
288 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(&child, db));
289 }
290 }
291 }
292 }
293 buffer
294 }
295
296 pub fn get_text_without_trivia(self, db: &dyn SyntaxGroup) -> String {
301 let trimmed_span = self.span_without_trivia(db);
302
303 self.get_text_of_span(db, trimmed_span)
304 }
305
306 pub fn get_text_of_span(self, db: &dyn SyntaxGroup, span: TextSpan) -> String {
312 let orig_span = self.span(db);
313 assert!(orig_span.contains(span));
314 let full_text = self.get_text(db);
315
316 let span_in_span = TextSpan {
317 start: (span.start - orig_span.start).as_offset(),
318 end: (span.end - orig_span.start).as_offset(),
319 };
320 span_in_span.take(&full_text).to_string()
321 }
322
323 pub fn descendants<'db>(
328 &self,
329 db: &'db dyn SyntaxGroup,
330 ) -> impl Iterator<Item = SyntaxNode> + 'db {
331 self.preorder(db).filter_map(|event| match event {
332 WalkEvent::Enter(node) => Some(node),
333 WalkEvent::Leave(_) => None,
334 })
335 }
336
337 pub fn preorder<'db>(&self, db: &'db dyn SyntaxGroup) -> Preorder<'db> {
340 Preorder::new(*self, db)
341 }
342
343 pub fn tokens<'a>(&'a self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = Self> + 'a {
345 self.preorder(db).filter_map(|event| match event {
346 WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
347 _ => None,
348 })
349 }
350
351 pub fn cast<T: TypedSyntaxNode>(self, db: &dyn SyntaxGroup) -> Option<T> {
353 T::cast(db, self)
354 }
355
356 pub fn ancestors<'a>(&self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = SyntaxNode> + 'a {
358 std::iter::successors(self.parent(db), |n| n.parent(db))
360 }
361
362 pub fn ancestors_with_self<'a>(
364 &self,
365 db: &'a dyn SyntaxGroup,
366 ) -> impl Iterator<Item = SyntaxNode> + 'a {
367 std::iter::successors(Some(*self), |n| n.parent(db))
368 }
369
370 pub fn is_ancestor(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
372 node.ancestors(db).any(|n| n == *self)
373 }
374
375 pub fn is_descendant(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
377 node.is_ancestor(db, self)
378 }
379
380 pub fn is_ancestor_or_self(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
382 node.ancestors_with_self(db).any(|n| n == *self)
383 }
384
385 pub fn is_descendant_or_self(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
387 node.is_ancestor_or_self(db, self)
388 }
389
390 pub fn ancestor_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
392 self.ancestors(db).find(|node| node.kind(db) == kind)
393 }
394
395 pub fn ancestor_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
397 self.ancestors(db).find_map(|node| T::cast(db, node))
398 }
399
400 pub fn parent_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
402 self.parent(db).filter(|node| node.kind(db) == kind)
403 }
404
405 pub fn parent_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
407 self.parent(db).and_then(|node| T::cast(db, node))
408 }
409
410 pub fn ancestor_of_kinds(
412 &self,
413 db: &dyn SyntaxGroup,
414 kinds: &[SyntaxKind],
415 ) -> Option<SyntaxNode> {
416 self.ancestors(db).find(|node| kinds.contains(&node.kind(db)))
417 }
418
419 pub fn parent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
421 Some(self.parent(db)?.kind(db))
422 }
423
424 pub fn grandparent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
426 Some(self.parent(db)?.parent(db)?.kind(db))
427 }
428}
429
430pub trait TypedSyntaxNode: Sized {
433 const OPTIONAL_KIND: Option<SyntaxKind>;
435 type StablePtr: TypedStablePtr;
436 type Green;
437 fn missing(db: &dyn SyntaxGroup) -> Self::Green;
438 fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
439 fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self>;
440 fn as_syntax_node(&self) -> SyntaxNode;
441 fn stable_ptr(&self, db: &dyn SyntaxGroup) -> Self::StablePtr;
442}
443
444pub trait Token: TypedSyntaxNode {
445 fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green;
446 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
447}
448
449pub trait Terminal: TypedSyntaxNode {
450 const KIND: SyntaxKind;
451 type TokenType: Token;
452 fn new_green(
453 db: &dyn SyntaxGroup,
454 leading_trivia: TriviaGreen,
455 token: <<Self as Terminal>::TokenType as TypedSyntaxNode>::Green,
456 trailing_trivia: TriviaGreen,
457 ) -> <Self as TypedSyntaxNode>::Green;
458 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
460 fn cast_token(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
462 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
463 Some(Self::from_syntax_node(db, node.parent(db)?))
464 } else {
465 None
466 }
467 }
468}
469
470pub trait TypedStablePtr {
472 type SyntaxNode: TypedSyntaxNode;
473 fn lookup(&self, db: &dyn SyntaxGroup) -> Self::SyntaxNode;
475 fn untyped(&self) -> SyntaxStablePtrId;
477}
478
479pub struct NodeTextFormatter<'a> {
481 pub node: &'a SyntaxNode,
483 pub db: &'a dyn SyntaxGroup,
485}
486impl Display for NodeTextFormatter<'_> {
487 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488 match &self.node.green_node(self.db).as_ref().details {
489 green::GreenNodeDetails::Token(text) => write!(f, "{text}")?,
490 green::GreenNodeDetails::Node { .. } => {
491 for child in self.node.get_children(self.db) {
492 write!(f, "{}", NodeTextFormatter { node: &child, db: self.db })?;
493 }
494 }
495 }
496 Ok(())
497 }
498}