use lady_deirdre::{
arena::Identifiable,
format::{AnnotationPriority, Style, TerminalString},
lexis::{SiteRef, SourceCode, ToSpan, Token, TokenCursor},
syntax::{AbstractNode, PolyRef, PolyVariant, RefKind},
};
use crate::{
format::ScriptSnippet,
runtime::ScriptOrigin,
syntax::{ScriptDoc, ScriptNode},
};
impl<P: PolyRef + ?Sized> PolyRefOrigin for P {}
pub(crate) trait PolyRefOrigin: PolyRef {
fn script_origin(&self, doc: &ScriptDoc, bounds: SpanBounds) -> ScriptOrigin {
match self.kind() {
RefKind::Token => ScriptOrigin::from(self.as_token_ref()),
RefKind::Node => match self.as_node_ref().deref(doc) {
Some(node) => node.script_origin(doc, bounds),
None => ScriptOrigin::default(),
},
}
}
fn script_display<'a>(&self, doc: &'a ScriptDoc, suffix: impl AsRef<str>) -> ScriptSnippet<'a> {
let span = self
.script_origin(doc, SpanBounds::Cover)
.to_site_span(doc)
.unwrap_or(0..0);
let mut caption = match self.as_variant() {
PolyVariant::Token(variant) => {
let name = variant
.deref(doc)
.map(|token| token.name())
.flatten()
.unwrap_or("?");
let inner =
format!("(token: {:?})", variant.entry).apply(Style::new().bright_black());
format!("${name}({inner})")
}
PolyVariant::Node(variant) => {
let name = variant
.deref(doc)
.map(|node| node.name())
.flatten()
.unwrap_or("?");
let inner =
format!("(node: {:?})", variant.entry).apply(Style::new().bright_black());
format!("{name}({inner})")
}
};
let suffix = suffix.as_ref();
if !suffix.is_empty() {
caption.push_str(&format!(" → {suffix}"));
}
let mut snippet = ScriptSnippet::from_doc(doc);
snippet
.set_caption(caption)
.annotate(span, AnnotationPriority::Default, "");
snippet
}
}
impl ScriptNode {
pub(crate) fn script_origin(&self, doc: &ScriptDoc, bounds: SpanBounds) -> ScriptOrigin {
match self {
Self::InlineComment { start, end, .. } | Self::MultilineComment { start, end, .. } => {
let mut span = ScriptOrigin::from(start);
if !bounds.is_header() {
match end.is_nil() {
true => span.unbound(),
false => span.union(&ScriptOrigin::from(end)),
}
}
span
}
Self::Root { .. } => {
if let SpanBounds::Footer = bounds {
let eoi = SiteRef::end_of(doc.id());
return ScriptOrigin::from(eoi..eoi);
}
doc.cursor(..).site_ref(0).into()
}
_ => match bounds {
SpanBounds::Header => {
for capture in self.children_iter() {
let origin = capture.script_origin(doc, SpanBounds::Header);
if origin.is_nil() {
continue;
}
return origin;
}
ScriptOrigin::nil()
}
SpanBounds::Footer => {
for capture in self.children_iter().rev() {
let origin = capture.script_origin(doc, SpanBounds::Footer);
if origin.is_nil() {
continue;
}
return origin;
}
ScriptOrigin::nil()
}
SpanBounds::Cover => {
let mut origin = self.script_origin(doc, SpanBounds::Header);
if origin.is_nil() {
return origin;
}
let footer = self.script_origin(doc, SpanBounds::Footer);
origin.union(&footer);
origin
}
},
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum SpanBounds {
Header,
Footer,
Cover,
}
impl SpanBounds {
#[inline(always)]
fn is_header(&self) -> bool {
match self {
Self::Header => true,
_ => false,
}
}
}