use std::fmt;
use std::ops::Range;
use chumsky::span::{SimpleSpan, Spanned};
pub(crate) type Span = SimpleSpan<usize>;
#[derive(Debug, Default)]
pub(crate) struct Document {
pub(crate) title: Option<String>,
pub(crate) subtitle: Option<String>,
pub(crate) description: Option<DescriptionBlock>,
pub(crate) styles: Vec<StyleDefinition>,
pub(crate) components: Vec<ComponentItem>,
pub(crate) edges: Vec<EdgeItem>,
pub(crate) legend: Option<LegendSection>,
}
#[derive(Debug)]
pub(crate) struct StyleDefinition {
pub(crate) name: Spanned<String>,
pub(crate) entries: Vec<Entry>,
}
#[derive(Debug)]
pub(crate) struct ComponentItem {
pub(crate) id: Spanned<String>,
pub(crate) display_name: Option<String>,
pub(crate) entries: Vec<Entry>,
pub(crate) description: Option<DescriptionBlock>,
pub(crate) children: Vec<ComponentItem>,
pub(crate) span: Span,
}
#[derive(Debug)]
pub(crate) struct EdgeItem {
pub(crate) references: Vec<Reference>,
pub(crate) label: Option<Spanned<String>>,
pub(crate) properties: Option<Spanned<Vec<Entry>>>,
pub(crate) description: Option<DescriptionBlock>,
}
#[derive(Debug)]
pub(crate) struct DescriptionBlock {
pub(crate) text: String,
pub(crate) line_starts: Vec<Option<usize>>,
pub(crate) span: Span,
}
#[derive(Debug, Clone)]
pub(crate) struct Reference {
pub(crate) segments: Vec<String>,
pub(crate) span: Span,
}
#[derive(Debug)]
pub(crate) struct LegendSection {
pub(crate) title: Option<String>,
pub(crate) items: Vec<LegendItem>,
}
#[derive(Debug)]
pub(crate) enum LegendItem {
Line(String),
KeyRow(LegendKeyRow),
}
#[derive(Debug)]
pub(crate) struct LegendKeyRow {
pub(crate) entries: Vec<Entry>,
pub(crate) description: String,
pub(crate) span: Span,
}
#[derive(Debug, Clone)]
pub(crate) enum Entry {
Style(Spanned<String>),
Assignment(Assignment),
}
#[derive(Debug, Clone)]
pub(crate) struct Assignment {
pub(crate) key: Spanned<String>,
pub(crate) value: Spanned<Value>,
}
#[derive(Debug, Clone)]
pub(crate) enum Value {
Quoted(String),
Bare(Vec<String>),
}
impl Entry {
pub(crate) fn relabelled(self, span: Span) -> Self {
match self {
Self::Style(style_name) => {
let relabelled_name = Spanned {
inner: style_name.inner,
span,
};
Self::Style(relabelled_name)
}
Self::Assignment(Assignment { key, value }) => {
let relabelled_key = Spanned {
inner: key.inner,
span,
};
let relabelled_value = Spanned {
inner: value.inner,
span,
};
let relabelled_assignment = Assignment {
key: relabelled_key,
value: relabelled_value,
};
Self::Assignment(relabelled_assignment)
}
}
}
}
impl DescriptionBlock {
pub(crate) fn source_span(&self, range: Range<usize>) -> Span {
let text_before = &self.text[..range.start];
let line_index = text_before.matches('\n').count();
let line_text_start = text_before.rfind('\n').map_or(0, |newline| newline + 1);
let line_source_start = self.line_starts.get(line_index).copied().flatten();
let Some(line_source_start) = line_source_start else {
return self.span;
};
let column = range.start - line_text_start;
let start = line_source_start + column;
let end = start + range.len();
Span::from(start..end)
}
}
impl Reference {
pub(crate) fn from_dotted(dotted: &str, span: Span) -> Self {
let segments = dotted.split('.').map(str::to_owned).collect();
Self { segments, span }
}
}
impl fmt::Display for Reference {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let dotted = self.segments.join(".");
formatter.write_str(&dotted)
}
}