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::default(),
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 let kind = child.kind(db);
232
233 if matches!(kind, SyntaxKind::Trivia) {
234 ast::Trivia::from_syntax_node(db, child.clone())
235 .elements(db)
236 .iter()
237 .for_each(|element| {
238 if !matches!(
239 element,
240 ast::Trivium::SingleLineComment(_)
241 | ast::Trivium::SingleLineDocComment(_)
242 | ast::Trivium::SingleLineInnerComment(_)
243 ) {
244 buffer.push_str(
245 &element
246 .as_syntax_node()
247 .get_text_without_all_comment_trivia(db),
248 );
249 }
250 });
251 } else {
252 buffer
253 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
254 }
255 }
256 }
257 }
258 buffer
259 }
260
261 pub fn get_text_without_trivia(self, db: &dyn SyntaxGroup) -> String {
266 let trimmed_span = self.span_without_trivia(db);
267
268 self.get_text_of_span(db, trimmed_span)
269 }
270
271 pub fn get_text_of_span(self, db: &dyn SyntaxGroup, span: TextSpan) -> String {
277 let orig_span = self.span(db);
278 assert!(orig_span.contains(span));
279 let full_text = self.get_text(db);
280 let zero_offset = TextOffset::default();
281
282 let span_in_span = TextSpan {
283 start: zero_offset.add_width(span.start - orig_span.start),
284 end: zero_offset.add_width(span.end - orig_span.start),
285 };
286 span_in_span.take(&full_text).to_string()
287 }
288
289 pub fn descendants<'db>(
294 &self,
295 db: &'db dyn SyntaxGroup,
296 ) -> impl Iterator<Item = SyntaxNode> + 'db {
297 self.preorder(db).filter_map(|event| match event {
298 WalkEvent::Enter(node) => Some(node),
299 WalkEvent::Leave(_) => None,
300 })
301 }
302
303 pub fn preorder<'db>(&self, db: &'db dyn SyntaxGroup) -> Preorder<'db> {
306 Preorder::new(self.clone(), db)
307 }
308
309 pub fn tokens<'a>(&'a self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = Self> + 'a {
311 self.preorder(db).filter_map(|event| match event {
312 WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
313 _ => None,
314 })
315 }
316}
317
318pub trait TypedSyntaxNode {
321 const OPTIONAL_KIND: Option<SyntaxKind>;
323 type StablePtr: TypedStablePtr;
324 type Green;
325 fn missing(db: &dyn SyntaxGroup) -> Self::Green;
326 fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
328 fn as_syntax_node(&self) -> SyntaxNode;
329 fn stable_ptr(&self) -> Self::StablePtr;
330}
331
332pub trait Token: TypedSyntaxNode {
333 fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green;
334 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
335}
336
337pub trait Terminal: TypedSyntaxNode {
338 const KIND: SyntaxKind;
339 type TokenType: Token;
340 fn new_green(
341 db: &dyn SyntaxGroup,
342 leading_trivia: TriviaGreen,
343 token: <<Self as Terminal>::TokenType as TypedSyntaxNode>::Green,
344 trailing_trivia: TriviaGreen,
345 ) -> <Self as TypedSyntaxNode>::Green;
346 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
348}
349
350pub trait TypedStablePtr {
352 type SyntaxNode: TypedSyntaxNode;
353 fn lookup(&self, db: &dyn SyntaxGroup) -> Self::SyntaxNode;
355 fn untyped(&self) -> SyntaxStablePtrId;
357}
358
359pub struct NodeTextFormatter<'a> {
361 pub node: &'a SyntaxNode,
363 pub db: &'a dyn SyntaxGroup,
365}
366impl Display for NodeTextFormatter<'_> {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 match &self.node.green_node(self.db).as_ref().details {
369 green::GreenNodeDetails::Token(text) => write!(f, "{text}")?,
370 green::GreenNodeDetails::Node { .. } => {
371 for child in self.db.get_children(self.node.clone()).iter() {
372 write!(f, "{}", NodeTextFormatter { node: child, db: self.db })?;
373 }
374 }
375 }
376 Ok(())
377 }
378}