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 utils;
29pub mod with_db;
30
31#[cfg(test)]
32mod ast_test;
33#[cfg(test)]
34mod test_utils;
35
36#[derive(Clone, Debug, Hash, PartialEq, Eq)]
38pub struct SyntaxNode(Arc<SyntaxNodeInner>);
39#[derive(Clone, Debug, Hash, PartialEq, Eq)]
40struct SyntaxNodeInner {
41 green: GreenId,
42 offset: TextOffset,
45 parent: Option<SyntaxNode>,
46 stable_ptr: SyntaxStablePtrId,
47}
48impl SyntaxNode {
49 pub fn new_root(db: &dyn SyntaxGroup, file_id: FileId, green: GreenId) -> Self {
50 let inner = SyntaxNodeInner {
51 green,
52 offset: TextOffset::START,
53 parent: None,
54 stable_ptr: SyntaxStablePtr::Root(file_id, green).intern(db),
55 };
56 Self(Arc::new(inner))
57 }
58
59 pub fn new_root_with_offset(
60 db: &dyn SyntaxGroup,
61 file_id: FileId,
62 green: GreenId,
63 initial_offset: Option<TextOffset>,
64 ) -> Self {
65 let inner = SyntaxNodeInner {
66 green,
67 offset: initial_offset.unwrap_or_default(),
68 parent: None,
69 stable_ptr: SyntaxStablePtr::Root(file_id, green).intern(db),
70 };
71 Self(Arc::new(inner))
72 }
73
74 pub fn offset(&self) -> TextOffset {
75 self.0.offset
76 }
77 pub fn width(&self, db: &dyn SyntaxGroup) -> TextWidth {
78 self.green_node(db).width()
79 }
80 pub fn kind(&self, db: &dyn SyntaxGroup) -> SyntaxKind {
81 self.green_node(db).kind
82 }
83 pub fn span(&self, db: &dyn SyntaxGroup) -> TextSpan {
84 let start = self.offset();
85 let end = start.add_width(self.width(db));
86 TextSpan { start, end }
87 }
88 pub fn text(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
90 match &self.green_node(db).details {
91 green::GreenNodeDetails::Token(text) => Some(text.clone()),
92 green::GreenNodeDetails::Node { .. } => None,
93 }
94 }
95 pub fn green_node(&self, db: &dyn SyntaxGroup) -> Arc<GreenNode> {
96 self.0.green.lookup_intern(db)
97 }
98 pub fn span_without_trivia(&self, db: &dyn SyntaxGroup) -> TextSpan {
99 let start = self.span_start_without_trivia(db);
100 let end = self.span_end_without_trivia(db);
101 TextSpan { start, end }
102 }
103 pub fn parent(&self) -> Option<SyntaxNode> {
104 self.0.parent.as_ref().cloned()
105 }
106 pub fn position_in_parent(&self, db: &dyn SyntaxGroup) -> Option<usize> {
109 let parent_green = self.parent()?.green_node(db);
110 let parent_children = parent_green.children();
111 let self_green_id = self.0.green;
112 parent_children.iter().position(|child| child == &self_green_id)
113 }
114 pub fn stable_ptr(&self) -> SyntaxStablePtrId {
115 self.0.stable_ptr
116 }
117
118 pub fn get_terminal_token(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
121 let green_node = self.green_node(db);
122 require(green_node.kind.is_terminal())?;
123 let token_node = db.get_children(self.clone())[1].clone();
125 Some(token_node)
126 }
127
128 pub fn span_start_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
129 let green_node = self.green_node(db);
130 match green_node.details {
131 green::GreenNodeDetails::Node { .. } => {
132 if let Some(token_node) = self.get_terminal_token(db) {
133 return token_node.offset();
134 }
135 let children = db.get_children(self.clone());
136 if let Some(child) =
137 children.iter().find(|child| child.width(db) != TextWidth::default())
138 {
139 child.span_start_without_trivia(db)
140 } else {
141 self.offset()
142 }
143 }
144 green::GreenNodeDetails::Token(_) => self.offset(),
145 }
146 }
147 pub fn span_end_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
148 let green_node = self.green_node(db);
149 match green_node.details {
150 green::GreenNodeDetails::Node { .. } => {
151 if let Some(token_node) = self.get_terminal_token(db) {
152 return token_node.span(db).end;
153 }
154 let children = &mut db.get_children(self.clone());
155 if let Some(child) =
156 children.iter().filter(|child| child.width(db) != TextWidth::default()).last()
157 {
158 child.span_end_without_trivia(db)
159 } else {
160 self.span(db).end
161 }
162 }
163 green::GreenNodeDetails::Token(_) => self.span(db).end,
164 }
165 }
166
167 pub fn lookup_offset(&self, db: &dyn SyntaxGroup, offset: TextOffset) -> SyntaxNode {
169 for child in db.get_children(self.clone()).iter() {
170 if child.offset().add_width(child.width(db)) > offset {
171 return child.lookup_offset(db, offset);
172 }
173 }
174 self.clone()
175 }
176
177 pub fn lookup_position(&self, db: &dyn SyntaxGroup, position: TextPosition) -> SyntaxNode {
179 match position.offset_in_file(db.upcast(), self.stable_ptr().file_id(db)) {
180 Some(offset) => self.lookup_offset(db, offset),
181 None => self.clone(),
182 }
183 }
184
185 pub fn get_text(&self, db: &dyn SyntaxGroup) -> String {
188 format!("{}", NodeTextFormatter { node: self, db })
189 }
190
191 pub fn get_text_without_inner_commentable_children(&self, db: &dyn SyntaxGroup) -> String {
196 let mut buffer = String::new();
197
198 match &self.green_node(db).as_ref().details {
199 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
200 green::GreenNodeDetails::Node { .. } => {
201 for child in db.get_children(self.clone()).iter() {
202 let kind = child.kind(db);
203
204 if !matches!(
207 kind,
208 SyntaxKind::FunctionWithBody
209 | SyntaxKind::ItemModule
210 | SyntaxKind::TraitItemFunction
211 ) {
212 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
213 child, db,
214 ));
215 }
216 }
217 }
218 }
219 buffer
220 }
221
222 pub fn get_text_without_all_comment_trivia(&self, db: &dyn SyntaxGroup) -> String {
225 let mut buffer = String::new();
226
227 match &self.green_node(db).as_ref().details {
228 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
229 green::GreenNodeDetails::Node { .. } => {
230 for child in db.get_children(self.clone()).iter() {
231 if let Some(trivia) = ast::Trivia::cast(db, child.clone()) {
232 trivia.elements(db).iter().for_each(|element| {
233 if !matches!(
234 element,
235 ast::Trivium::SingleLineComment(_)
236 | ast::Trivium::SingleLineDocComment(_)
237 | ast::Trivium::SingleLineInnerComment(_)
238 ) {
239 buffer.push_str(
240 &element
241 .as_syntax_node()
242 .get_text_without_all_comment_trivia(db),
243 );
244 }
245 });
246 } else {
247 buffer
248 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
249 }
250 }
251 }
252 }
253 buffer
254 }
255
256 pub fn get_text_without_trivia(self, db: &dyn SyntaxGroup) -> String {
261 let trimmed_span = self.span_without_trivia(db);
262
263 self.get_text_of_span(db, trimmed_span)
264 }
265
266 pub fn get_text_of_span(self, db: &dyn SyntaxGroup, span: TextSpan) -> String {
272 let orig_span = self.span(db);
273 assert!(orig_span.contains(span));
274 let full_text = self.get_text(db);
275
276 let span_in_span = TextSpan {
277 start: (span.start - orig_span.start).as_offset(),
278 end: (span.end - orig_span.start).as_offset(),
279 };
280 span_in_span.take(&full_text).to_string()
281 }
282
283 pub fn descendants<'db>(
288 &self,
289 db: &'db dyn SyntaxGroup,
290 ) -> impl Iterator<Item = SyntaxNode> + 'db {
291 self.preorder(db).filter_map(|event| match event {
292 WalkEvent::Enter(node) => Some(node),
293 WalkEvent::Leave(_) => None,
294 })
295 }
296
297 pub fn preorder<'db>(&self, db: &'db dyn SyntaxGroup) -> Preorder<'db> {
300 Preorder::new(self.clone(), db)
301 }
302
303 pub fn tokens<'a>(&'a self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = Self> + 'a {
305 self.preorder(db).filter_map(|event| match event {
306 WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
307 _ => None,
308 })
309 }
310}
311
312pub trait TypedSyntaxNode: Sized {
315 const OPTIONAL_KIND: Option<SyntaxKind>;
317 type StablePtr: TypedStablePtr;
318 type Green;
319 fn missing(db: &dyn SyntaxGroup) -> Self::Green;
320 fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
321 fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self>;
322 fn as_syntax_node(&self) -> SyntaxNode;
323 fn stable_ptr(&self) -> Self::StablePtr;
324}
325
326pub trait Token: TypedSyntaxNode {
327 fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green;
328 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
329}
330
331pub trait Terminal: TypedSyntaxNode {
332 const KIND: SyntaxKind;
333 type TokenType: Token;
334 fn new_green(
335 db: &dyn SyntaxGroup,
336 leading_trivia: TriviaGreen,
337 token: <<Self as Terminal>::TokenType as TypedSyntaxNode>::Green,
338 trailing_trivia: TriviaGreen,
339 ) -> <Self as TypedSyntaxNode>::Green;
340 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
342 fn cast_token(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
344 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
345 Some(Self::from_syntax_node(db, node.parent()?))
346 } else {
347 None
348 }
349 }
350}
351
352pub trait TypedStablePtr {
354 type SyntaxNode: TypedSyntaxNode;
355 fn lookup(&self, db: &dyn SyntaxGroup) -> Self::SyntaxNode;
357 fn untyped(&self) -> SyntaxStablePtrId;
359}
360
361pub struct NodeTextFormatter<'a> {
363 pub node: &'a SyntaxNode,
365 pub db: &'a dyn SyntaxGroup,
367}
368impl Display for NodeTextFormatter<'_> {
369 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370 match &self.node.green_node(self.db).as_ref().details {
371 green::GreenNodeDetails::Token(text) => write!(f, "{text}")?,
372 green::GreenNodeDetails::Node { .. } => {
373 for child in self.db.get_children(self.node.clone()).iter() {
374 write!(f, "{}", NodeTextFormatter { node: child, db: self.db })?;
375 }
376 }
377 }
378 Ok(())
379 }
380}