use std::fmt::{self, Debug, Display};
use educe::Educe;
use miette::Diagnostic;
use super::err::{ToASTError, ToASTErrorKind};
use super::Loc;
#[derive(Educe, Debug, Clone)]
#[educe(PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct Node<T> {
pub node: T,
#[educe(PartialEq(ignore))]
#[educe(PartialOrd(ignore))]
#[educe(Hash(ignore))]
pub loc: Option<Loc>,
}
impl<T> Node<T> {
pub fn with_source_loc(node: T, loc: Loc) -> Self {
Node {
node,
loc: Some(loc),
}
}
pub fn with_maybe_source_loc(node: T, loc: Option<Loc>) -> Self {
Node { node, loc }
}
pub fn new(node: T) -> Self {
Node { node, loc: None }
}
pub fn map<R>(self, f: impl FnOnce(T) -> R) -> Node<R> {
Node {
node: f(self.node),
loc: self.loc,
}
}
pub fn as_ref(&self) -> Node<&T> {
Node {
node: &self.node,
loc: self.loc.clone(),
}
}
pub fn as_mut(&mut self) -> Node<&mut T> {
Node {
node: &mut self.node,
loc: self.loc.clone(),
}
}
pub fn into_inner(self) -> (T, Option<Loc>) {
(self.node, self.loc)
}
pub fn to_ast_err(&self, error_kind: impl Into<ToASTErrorKind>) -> ToASTError {
ToASTError::new(error_kind.into(), self.loc.clone())
}
pub fn loc(&self) -> Option<&Loc> {
self.loc.as_ref()
}
}
impl<T: Clone> Node<&T> {
pub fn cloned(self) -> Node<T> {
self.map(|value| value.clone())
}
}
impl<T: Copy> Node<&T> {
pub fn copied(self) -> Node<T> {
self.map(|value| *value)
}
}
impl<T: Display> Display for Node<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.node, f)
}
}
impl<T: std::error::Error> std::error::Error for Node<T> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.node.source()
}
fn description(&self) -> &str {
#[expect(
deprecated,
reason = "description() is deprecated but we still want to forward it"
)]
self.node.description()
}
fn cause(&self) -> Option<&dyn std::error::Error> {
#[expect(
deprecated,
reason = "cause() is deprecated but we still want to forward it"
)]
self.node.cause()
}
}
impl<T: Diagnostic> Diagnostic for Node<T> {
impl_diagnostic_from_source_loc_opt_field!(loc);
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.node.code()
}
fn severity(&self) -> Option<miette::Severity> {
self.node.severity()
}
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.node.help()
}
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.node.url()
}
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
self.node.related()
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.node.diagnostic_source()
}
}
impl<T> Node<Option<T>> {
pub fn as_inner(&self) -> Option<&T> {
self.node.as_ref()
}
pub fn collapse(&self) -> Option<Node<&T>> {
self.node.as_ref().map(|node| Node {
node,
loc: self.loc.clone(),
})
}
pub fn apply<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(&T, Option<&Loc>) -> Option<R>,
{
f(self.node.as_ref()?, self.loc.as_ref())
}
pub fn into_apply<F, R>(self, f: F) -> Option<R>
where
F: FnOnce(T, Option<Loc>) -> Option<R>,
{
f(self.node?, self.loc)
}
pub fn try_as_inner(&self) -> Result<&T, ToASTError> {
self.node
.as_ref()
.ok_or_else(|| self.to_ast_err(ToASTErrorKind::EmptyNodeInvariantViolation))
}
pub fn try_into_inner(self) -> Result<T, ToASTError> {
self.node
.ok_or_else(|| ToASTError::new(ToASTErrorKind::EmptyNodeInvariantViolation, self.loc))
}
}