use crate::core::errors::DocumentError;
use crate::core::{document::Document, errors::PositionError};
use downcast_rs::{impl_downcast, DowncastSync};
use std::cmp::Ordering;
use tree_sitter::Node;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AstNodeId<T> {
pub id: usize,
_marker: std::marker::PhantomData<T>,
}
impl<'a, T: AstNode> AstNodeId<T> {
pub(crate) fn new(id: usize) -> Self {
Self {
id,
_marker: std::marker::PhantomData,
}
}
pub fn cast(&self, nodes: &'a Vec<Box<dyn AstNode>>) -> &'a T {
debug_assert!(nodes.is_sorted());
match nodes[self.id].downcast_ref::<T>() {
Some(node) => node,
None => panic!(
"Invalid cast of AstNodeId of id {} to {}",
self.id,
std::any::type_name::<T>(),
),
}
}
}
pub trait AstNode: std::fmt::Debug + Send + Sync + DowncastSync {
fn contains(node: &Node) -> bool
where
Self: Sized;
fn lower(&self) -> &dyn AstNode;
fn get_id(&self) -> usize;
#[allow(clippy::borrowed_box)]
fn get_parent_id(&self) -> Option<usize>;
fn get_range(&self) -> &tree_sitter::Range;
fn get_lsp_range(&self, document: &Document) -> Result<lsp_types_max::Range, DocumentError> {
document.denormalize_range(self.get_range())
}
fn is_missing(&self) -> bool;
fn get_start_position(&self) -> lsp_types_max::Position {
let range = self.get_range();
lsp_types_max::Position {
line: range.start_point.row as u32,
character: range.start_point.column as u32,
}
}
fn get_end_position(&self) -> lsp_types_max::Position {
let range = self.get_range();
lsp_types_max::Position {
line: range.end_point.row as u32,
character: range.end_point.column as u32,
}
}
fn get_text<'a>(&self, source_code: &'a [u8]) -> Result<&'a str, PositionError> {
let range = self.get_range();
let range = range.start_byte..range.end_byte;
match source_code.get(range.start..range.end) {
Some(text) => match std::str::from_utf8(text) {
Ok(text) => Ok(text),
Err(utf8_error) => Err(PositionError::UTF8Error { range, utf8_error }),
},
None => Err(PositionError::WrongTextRange { range }),
}
}
#[allow(clippy::borrowed_box)]
fn get_parent<'a>(&'a self, nodes: &'a [Box<dyn AstNode>]) -> Option<&'a Box<dyn AstNode>> {
match nodes.first() {
Some(first) => {
assert_eq!(
first.get_id(),
0,
"get_parent called on an unsorted node list"
);
nodes.get(self.get_parent_id()?)
}
None => None,
}
}
}
impl_downcast!(AstNode);
impl PartialEq for dyn AstNode {
fn eq(&self, other: &Self) -> bool {
self.get_range().eq(other.get_range()) && self.get_id().eq(&other.get_id())
}
}
impl Eq for dyn AstNode {}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd for dyn AstNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.get_id().cmp(&other.get_id()))
}
}
impl Ord for dyn AstNode {
fn cmp(&self, other: &Self) -> Ordering {
self.get_id().cmp(&other.get_id())
}
}