use core::hash::Hash;
use std::fmt::Display;
use std::sync::Arc;
use cairo_lang_filesystem::ids::FileId;
use cairo_lang_filesystem::span::{TextOffset, TextPosition, TextSpan, TextWidth};
use cairo_lang_utils::{Intern, LookupIntern, require};
use smol_str::SmolStr;
use self::ast::TriviaGreen;
use self::db::SyntaxGroup;
use self::green::GreenNode;
use self::ids::{GreenId, SyntaxStablePtrId};
use self::kind::SyntaxKind;
use self::stable_ptr::SyntaxStablePtr;
use crate::node::iter::{Preorder, WalkEvent};
pub mod ast;
pub mod db;
pub mod element_list;
pub mod green;
pub mod helpers;
pub mod ids;
pub mod iter;
pub mod key_fields;
pub mod kind;
pub mod stable_ptr;
pub mod utils;
#[cfg(test)]
mod ast_test;
#[cfg(test)]
mod test_utils;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SyntaxNode(Arc<SyntaxNodeInner>);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct SyntaxNodeInner {
green: GreenId,
offset: TextOffset,
parent: Option<SyntaxNode>,
stable_ptr: SyntaxStablePtrId,
}
impl SyntaxNode {
pub fn new_root(db: &dyn SyntaxGroup, file_id: FileId, green: GreenId) -> Self {
let inner = SyntaxNodeInner {
green,
offset: TextOffset::default(),
parent: None,
stable_ptr: SyntaxStablePtr::Root(file_id, green).intern(db),
};
Self(Arc::new(inner))
}
pub fn offset(&self) -> TextOffset {
self.0.offset
}
pub fn width(&self, db: &dyn SyntaxGroup) -> TextWidth {
self.green_node(db).width()
}
pub fn kind(&self, db: &dyn SyntaxGroup) -> SyntaxKind {
self.green_node(db).kind
}
pub fn span(&self, db: &dyn SyntaxGroup) -> TextSpan {
let start = self.offset();
let end = start.add_width(self.width(db));
TextSpan { start, end }
}
pub fn text(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
match &self.green_node(db).details {
green::GreenNodeDetails::Token(text) => Some(text.clone()),
green::GreenNodeDetails::Node { .. } => None,
}
}
pub fn green_node(&self, db: &dyn SyntaxGroup) -> Arc<GreenNode> {
self.0.green.lookup_intern(db)
}
pub fn span_without_trivia(&self, db: &dyn SyntaxGroup) -> TextSpan {
let start = self.span_start_without_trivia(db);
let end = self.span_end_without_trivia(db);
TextSpan { start, end }
}
pub fn parent(&self) -> Option<SyntaxNode> {
self.0.parent.as_ref().cloned()
}
pub fn position_in_parent(&self, db: &dyn SyntaxGroup) -> Option<usize> {
let parent_green = self.parent()?.green_node(db);
let parent_children = parent_green.children();
let self_green_id = self.0.green;
parent_children.iter().position(|child| child == &self_green_id)
}
pub fn stable_ptr(&self) -> SyntaxStablePtrId {
self.0.stable_ptr
}
pub fn get_terminal_token(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
let green_node = self.green_node(db);
require(green_node.kind.is_terminal())?;
let token_node = db.get_children(self.clone())[1].clone();
Some(token_node)
}
pub fn span_start_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
let green_node = self.green_node(db);
match green_node.details {
green::GreenNodeDetails::Node { .. } => {
if let Some(token_node) = self.get_terminal_token(db) {
return token_node.offset();
}
let children = db.get_children(self.clone());
if let Some(child) =
children.iter().find(|child| child.width(db) != TextWidth::default())
{
child.span_start_without_trivia(db)
} else {
self.offset()
}
}
green::GreenNodeDetails::Token(_) => self.offset(),
}
}
pub fn span_end_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
let green_node = self.green_node(db);
match green_node.details {
green::GreenNodeDetails::Node { .. } => {
if let Some(token_node) = self.get_terminal_token(db) {
return token_node.span(db).end;
}
let children = &mut db.get_children(self.clone());
if let Some(child) =
children.iter().filter(|child| child.width(db) != TextWidth::default()).last()
{
child.span_end_without_trivia(db)
} else {
self.span(db).end
}
}
green::GreenNodeDetails::Token(_) => self.span(db).end,
}
}
pub fn lookup_offset(&self, db: &dyn SyntaxGroup, offset: TextOffset) -> SyntaxNode {
for child in db.get_children(self.clone()).iter() {
if child.offset().add_width(child.width(db)) > offset {
return child.lookup_offset(db, offset);
}
}
self.clone()
}
pub fn lookup_position(&self, db: &dyn SyntaxGroup, position: TextPosition) -> SyntaxNode {
match position.offset_in_file(db.upcast(), self.stable_ptr().file_id(db)) {
Some(offset) => self.lookup_offset(db, offset),
None => self.clone(),
}
}
pub fn get_text(&self, db: &dyn SyntaxGroup) -> String {
format!("{}", NodeTextFormatter { node: self, db })
}
pub fn get_text_without_inner_commentable_children(&self, db: &dyn SyntaxGroup) -> String {
let mut buffer = String::new();
match &self.green_node(db).as_ref().details {
green::GreenNodeDetails::Token(text) => buffer.push_str(text),
green::GreenNodeDetails::Node { .. } => {
for child in db.get_children(self.clone()).iter() {
let kind = child.kind(db);
if !matches!(
kind,
SyntaxKind::FunctionWithBody
| SyntaxKind::ItemModule
| SyntaxKind::TraitItemFunction
) {
buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
child, db,
));
}
}
}
}
buffer
}
pub fn get_text_without_all_comment_trivia(&self, db: &dyn SyntaxGroup) -> String {
let mut buffer = String::new();
match &self.green_node(db).as_ref().details {
green::GreenNodeDetails::Token(text) => buffer.push_str(text),
green::GreenNodeDetails::Node { .. } => {
for child in db.get_children(self.clone()).iter() {
let kind = child.kind(db);
if matches!(kind, SyntaxKind::Trivia) {
ast::Trivia::from_syntax_node(db, child.clone())
.elements(db)
.iter()
.for_each(|element| {
if !matches!(
element,
ast::Trivium::SingleLineComment(_)
| ast::Trivium::SingleLineDocComment(_)
| ast::Trivium::SingleLineInnerComment(_)
) {
buffer.push_str(
&element
.as_syntax_node()
.get_text_without_all_comment_trivia(db),
);
}
});
} else {
buffer
.push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
}
}
}
}
buffer
}
pub fn get_text_without_trivia(self, db: &dyn SyntaxGroup) -> String {
let trimmed_span = self.span_without_trivia(db);
self.get_text_of_span(db, trimmed_span)
}
pub fn get_text_of_span(self, db: &dyn SyntaxGroup, span: TextSpan) -> String {
let orig_span = self.span(db);
assert!(orig_span.contains(span));
let full_text = self.get_text(db);
let zero_offset = TextOffset::default();
let span_in_span = TextSpan {
start: zero_offset.add_width(span.start - orig_span.start),
end: zero_offset.add_width(span.end - orig_span.start),
};
span_in_span.take(&full_text).to_string()
}
pub fn descendants<'db>(
&self,
db: &'db dyn SyntaxGroup,
) -> impl Iterator<Item = SyntaxNode> + 'db {
self.preorder(db).filter_map(|event| match event {
WalkEvent::Enter(node) => Some(node),
WalkEvent::Leave(_) => None,
})
}
pub fn preorder<'db>(&self, db: &'db dyn SyntaxGroup) -> Preorder<'db> {
Preorder::new(self.clone(), db)
}
pub fn tokens<'a>(&'a self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = Self> + 'a {
self.preorder(db).filter_map(|event| match event {
WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
_ => None,
})
}
}
pub trait TypedSyntaxNode {
const OPTIONAL_KIND: Option<SyntaxKind>;
type StablePtr: TypedStablePtr;
type Green;
fn missing(db: &dyn SyntaxGroup) -> Self::Green;
fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
fn as_syntax_node(&self) -> SyntaxNode;
fn stable_ptr(&self) -> Self::StablePtr;
}
pub trait Token: TypedSyntaxNode {
fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green;
fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
}
pub trait Terminal: TypedSyntaxNode {
const KIND: SyntaxKind;
type TokenType: Token;
fn new_green(
db: &dyn SyntaxGroup,
leading_trivia: TriviaGreen,
token: <<Self as Terminal>::TokenType as TypedSyntaxNode>::Green,
trailing_trivia: TriviaGreen,
) -> <Self as TypedSyntaxNode>::Green;
fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
}
pub trait TypedStablePtr {
type SyntaxNode: TypedSyntaxNode;
fn lookup(&self, db: &dyn SyntaxGroup) -> Self::SyntaxNode;
fn untyped(&self) -> SyntaxStablePtrId;
}
pub struct NodeTextFormatter<'a> {
pub node: &'a SyntaxNode,
pub db: &'a dyn SyntaxGroup,
}
impl Display for NodeTextFormatter<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.node.green_node(self.db).as_ref().details {
green::GreenNodeDetails::Token(text) => write!(f, "{text}")?,
green::GreenNodeDetails::Node { .. } => {
for child in self.db.get_children(self.node.clone()).iter() {
write!(f, "{}", NodeTextFormatter { node: child, db: self.db })?;
}
}
}
Ok(())
}
}