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::{Intern, LookupIntern, require};
8use smol_str::SmolStr;
9
10use self::ast::TriviaGreen;
11use self::db::SyntaxGroup;
12use self::green::GreenNode;
13use self::ids::{GreenId, SyntaxStablePtrId};
14use self::kind::SyntaxKind;
15use self::stable_ptr::SyntaxStablePtr;
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(Clone, Debug, Hash, PartialEq, Eq)]
37pub struct SyntaxNode(Arc<SyntaxNodeInner>);
38#[derive(Clone, Debug, Hash, PartialEq, Eq)]
39struct SyntaxNodeInner {
40 green: GreenId,
41 offset: TextOffset,
44 parent: Option<SyntaxNode>,
45 stable_ptr: SyntaxStablePtrId,
46}
47impl SyntaxNode {
48 pub fn new_root(db: &dyn SyntaxGroup, file_id: FileId, green: GreenId) -> Self {
49 let inner = SyntaxNodeInner {
50 green,
51 offset: TextOffset::START,
52 parent: None,
53 stable_ptr: SyntaxStablePtr::Root(file_id, green).intern(db),
54 };
55 Self(Arc::new(inner))
56 }
57
58 pub fn new_root_with_offset(
59 db: &dyn SyntaxGroup,
60 file_id: FileId,
61 green: GreenId,
62 initial_offset: Option<TextOffset>,
63 ) -> Self {
64 let inner = SyntaxNodeInner {
65 green,
66 offset: initial_offset.unwrap_or_default(),
67 parent: None,
68 stable_ptr: SyntaxStablePtr::Root(file_id, green).intern(db),
69 };
70 Self(Arc::new(inner))
71 }
72
73 pub fn offset(&self) -> TextOffset {
74 self.0.offset
75 }
76 pub fn width(&self, db: &dyn SyntaxGroup) -> TextWidth {
77 self.green_node(db).width()
78 }
79 pub fn kind(&self, db: &dyn SyntaxGroup) -> SyntaxKind {
80 self.green_node(db).kind
81 }
82 pub fn span(&self, db: &dyn SyntaxGroup) -> TextSpan {
83 let start = self.offset();
84 let end = start.add_width(self.width(db));
85 TextSpan { start, end }
86 }
87 pub fn text(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
89 match &self.green_node(db).details {
90 green::GreenNodeDetails::Token(text) => Some(text.clone()),
91 green::GreenNodeDetails::Node { .. } => None,
92 }
93 }
94 pub fn green_node(&self, db: &dyn SyntaxGroup) -> Arc<GreenNode> {
95 self.0.green.lookup_intern(db)
96 }
97 pub fn span_without_trivia(&self, db: &dyn SyntaxGroup) -> TextSpan {
98 let start = self.span_start_without_trivia(db);
99 let end = self.span_end_without_trivia(db);
100 TextSpan { start, end }
101 }
102 pub fn parent(&self) -> Option<SyntaxNode> {
103 self.0.parent.as_ref().cloned()
104 }
105 pub fn stable_ptr(&self) -> SyntaxStablePtrId {
106 self.0.stable_ptr
107 }
108
109 pub fn get_terminal_token(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
112 let green_node = self.green_node(db);
113 require(green_node.kind.is_terminal())?;
114 let token_node = db.get_children(self.clone())[1].clone();
116 Some(token_node)
117 }
118
119 pub fn span_start_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
120 let green_node = self.green_node(db);
121 match green_node.details {
122 green::GreenNodeDetails::Node { .. } => {
123 if let Some(token_node) = self.get_terminal_token(db) {
124 return token_node.offset();
125 }
126 let children = db.get_children(self.clone());
127 if let Some(child) =
128 children.iter().find(|child| child.width(db) != TextWidth::default())
129 {
130 child.span_start_without_trivia(db)
131 } else {
132 self.offset()
133 }
134 }
135 green::GreenNodeDetails::Token(_) => self.offset(),
136 }
137 }
138 pub fn span_end_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
139 let green_node = self.green_node(db);
140 match green_node.details {
141 green::GreenNodeDetails::Node { .. } => {
142 if let Some(token_node) = self.get_terminal_token(db) {
143 return token_node.span(db).end;
144 }
145 let children = &mut db.get_children(self.clone());
146 if let Some(child) = children
147 .iter()
148 .filter(|child| child.width(db) != TextWidth::default())
149 .next_back()
150 {
151 child.span_end_without_trivia(db)
152 } else {
153 self.span(db).end
154 }
155 }
156 green::GreenNodeDetails::Token(_) => self.span(db).end,
157 }
158 }
159
160 pub fn lookup_offset(&self, db: &dyn SyntaxGroup, offset: TextOffset) -> SyntaxNode {
162 for child in db.get_children(self.clone()).iter() {
163 if child.offset().add_width(child.width(db)) > offset {
164 return child.lookup_offset(db, offset);
165 }
166 }
167 self.clone()
168 }
169
170 pub fn lookup_position(&self, db: &dyn SyntaxGroup, position: TextPosition) -> SyntaxNode {
172 match position.offset_in_file(db.upcast(), self.stable_ptr().file_id(db)) {
173 Some(offset) => self.lookup_offset(db, offset),
174 None => self.clone(),
175 }
176 }
177
178 pub fn get_text(&self, db: &dyn SyntaxGroup) -> String {
181 format!("{}", NodeTextFormatter { node: self, db })
182 }
183
184 pub fn get_text_without_inner_commentable_children(&self, db: &dyn SyntaxGroup) -> String {
189 let mut buffer = String::new();
190
191 match &self.green_node(db).as_ref().details {
192 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
193 green::GreenNodeDetails::Node { .. } => {
194 for child in db.get_children(self.clone()).iter() {
195 let kind = child.kind(db);
196
197 if !matches!(
200 kind,
201 SyntaxKind::FunctionWithBody
202 | SyntaxKind::ItemModule
203 | SyntaxKind::TraitItemFunction
204 ) {
205 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
206 child, db,
207 ));
208 }
209 }
210 }
211 }
212 buffer
213 }
214
215 pub fn get_text_without_all_comment_trivia(&self, db: &dyn SyntaxGroup) -> String {
218 let mut buffer = String::new();
219
220 match &self.green_node(db).as_ref().details {
221 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
222 green::GreenNodeDetails::Node { .. } => {
223 for child in db.get_children(self.clone()).iter() {
224 if let Some(trivia) = ast::Trivia::cast(db, child.clone()) {
225 trivia.elements(db).iter().for_each(|element| {
226 if !matches!(
227 element,
228 ast::Trivium::SingleLineComment(_)
229 | ast::Trivium::SingleLineDocComment(_)
230 | ast::Trivium::SingleLineInnerComment(_)
231 ) {
232 buffer.push_str(
233 &element
234 .as_syntax_node()
235 .get_text_without_all_comment_trivia(db),
236 );
237 }
238 });
239 } else {
240 buffer
241 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
242 }
243 }
244 }
245 }
246 buffer
247 }
248
249 pub fn get_text_without_trivia(self, db: &dyn SyntaxGroup) -> String {
254 let trimmed_span = self.span_without_trivia(db);
255
256 self.get_text_of_span(db, trimmed_span)
257 }
258
259 pub fn get_text_of_span(self, db: &dyn SyntaxGroup, span: TextSpan) -> String {
265 let orig_span = self.span(db);
266 assert!(orig_span.contains(span));
267 let full_text = self.get_text(db);
268
269 let span_in_span = TextSpan {
270 start: (span.start - orig_span.start).as_offset(),
271 end: (span.end - orig_span.start).as_offset(),
272 };
273 span_in_span.take(&full_text).to_string()
274 }
275
276 pub fn descendants<'db>(
281 &self,
282 db: &'db dyn SyntaxGroup,
283 ) -> impl Iterator<Item = SyntaxNode> + 'db {
284 self.preorder(db).filter_map(|event| match event {
285 WalkEvent::Enter(node) => Some(node),
286 WalkEvent::Leave(_) => None,
287 })
288 }
289
290 pub fn preorder<'db>(&self, db: &'db dyn SyntaxGroup) -> Preorder<'db> {
293 Preorder::new(self.clone(), db)
294 }
295
296 pub fn tokens<'a>(&'a self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = Self> + 'a {
298 self.preorder(db).filter_map(|event| match event {
299 WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
300 _ => None,
301 })
302 }
303
304 pub fn cast<T: TypedSyntaxNode>(self, db: &dyn SyntaxGroup) -> Option<T> {
306 T::cast(db, self)
307 }
308
309 pub fn ancestors(&self) -> impl Iterator<Item = SyntaxNode> {
311 std::iter::successors(self.parent(), SyntaxNode::parent)
313 }
314
315 pub fn ancestors_with_self(&self) -> impl Iterator<Item = SyntaxNode> {
317 std::iter::successors(Some(self.clone()), SyntaxNode::parent)
318 }
319
320 pub fn is_ancestor(&self, node: &SyntaxNode) -> bool {
322 node.ancestors().any(|n| n == *self)
323 }
324
325 pub fn is_descendant(&self, node: &SyntaxNode) -> bool {
327 node.is_ancestor(self)
328 }
329
330 pub fn is_ancestor_or_self(&self, node: &SyntaxNode) -> bool {
332 node.ancestors_with_self().any(|n| n == *self)
333 }
334
335 pub fn is_descendant_or_self(&self, node: &SyntaxNode) -> bool {
337 node.is_ancestor_or_self(self)
338 }
339
340 pub fn ancestor_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
342 self.ancestors().find(|node| node.kind(db) == kind)
343 }
344
345 pub fn ancestor_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
347 self.ancestors().find_map(|node| T::cast(db, node))
348 }
349
350 pub fn parent_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
352 self.parent().filter(|node| node.kind(db) == kind)
353 }
354
355 pub fn parent_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
357 self.parent().and_then(|node| T::cast(db, node))
358 }
359
360 pub fn ancestor_of_kinds(
362 &self,
363 db: &dyn SyntaxGroup,
364 kinds: &[SyntaxKind],
365 ) -> Option<SyntaxNode> {
366 self.ancestors().find(|node| kinds.contains(&node.kind(db)))
367 }
368
369 pub fn parent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
371 Some(self.parent()?.kind(db))
372 }
373
374 pub fn grandparent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
376 Some(self.parent()?.parent()?.kind(db))
377 }
378}
379
380pub trait TypedSyntaxNode: Sized {
383 const OPTIONAL_KIND: Option<SyntaxKind>;
385 type StablePtr: TypedStablePtr;
386 type Green;
387 fn missing(db: &dyn SyntaxGroup) -> Self::Green;
388 fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
389 fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self>;
390 fn as_syntax_node(&self) -> SyntaxNode;
391 fn stable_ptr(&self) -> Self::StablePtr;
392}
393
394pub trait Token: TypedSyntaxNode {
395 fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green;
396 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
397}
398
399pub trait Terminal: TypedSyntaxNode {
400 const KIND: SyntaxKind;
401 type TokenType: Token;
402 fn new_green(
403 db: &dyn SyntaxGroup,
404 leading_trivia: TriviaGreen,
405 token: <<Self as Terminal>::TokenType as TypedSyntaxNode>::Green,
406 trailing_trivia: TriviaGreen,
407 ) -> <Self as TypedSyntaxNode>::Green;
408 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
410 fn cast_token(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
412 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
413 Some(Self::from_syntax_node(db, node.parent()?))
414 } else {
415 None
416 }
417 }
418}
419
420pub trait TypedStablePtr {
422 type SyntaxNode: TypedSyntaxNode;
423 fn lookup(&self, db: &dyn SyntaxGroup) -> Self::SyntaxNode;
425 fn untyped(&self) -> SyntaxStablePtrId;
427}
428
429pub struct NodeTextFormatter<'a> {
431 pub node: &'a SyntaxNode,
433 pub db: &'a dyn SyntaxGroup,
435}
436impl Display for NodeTextFormatter<'_> {
437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438 match &self.node.green_node(self.db).as_ref().details {
439 green::GreenNodeDetails::Token(text) => write!(f, "{text}")?,
440 green::GreenNodeDetails::Node { .. } => {
441 for child in self.db.get_children(self.node.clone()).iter() {
442 write!(f, "{}", NodeTextFormatter { node: child, db: self.db })?;
443 }
444 }
445 }
446 Ok(())
447 }
448}