use std::fmt;
use std::ops::Range;
use strum::{EnumString, IntoStaticStr};
use svugy_model::model::ByteRange;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct D2Document {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Statement {
Shape(ShapeDeclaration),
Edge(EdgeDeclaration),
Field(FieldAssignment),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShapeDeclaration {
pub path: KeyPath,
pub label: Option<Scalar>,
pub body: Vec<Statement>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EdgeDeclaration {
pub source: KeyPath,
pub links: Vec<EdgeLink>,
pub label: Option<Scalar>,
pub fields: Vec<Field>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EdgeLink {
pub operator: EdgeOperator,
pub operator_span: Span,
pub target: KeyPath,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeOperator {
Forward,
Backward,
Bidirectional,
Undirected,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldAssignment {
pub target: KeyPath,
pub field: Field,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Field {
Shape(Scalar),
Label(Scalar),
Direction(Scalar),
Link(Scalar),
Tooltip(Scalar),
Style(Vec<StyleProperty>),
Svugy(Vec<SvugyProperty>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct StyleProperty {
pub name: KeySegment,
pub value: Scalar,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SvugyProperty {
pub attribute_path: Vec<String>,
pub value: SvugyValue,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SvugyValue {
Scalar(Scalar),
List { items: Vec<Scalar>, span: Span },
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct KeyPath {
pub parent_references: usize,
pub segments: Vec<KeySegment>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct KeySegment {
pub name: String,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Scalar {
pub text: String,
pub span: Span,
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Span {
pub start: usize,
pub end: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, IntoStaticStr)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub(crate) enum Keyword {
Shape,
Label,
Direction,
Link,
Style,
Vars,
Class,
Classes,
Layers,
Scenarios,
Steps,
Icon,
Constraint,
Tooltip,
Near,
Width,
Height,
Top,
Left,
GridRows,
GridColumns,
GridGap,
VerticalGap,
HorizontalGap,
SourceArrowhead,
TargetArrowhead,
}
impl Field {
pub(crate) fn keyword(&self) -> Keyword {
match self {
Self::Shape(_shape) => Keyword::Shape,
Self::Label(_label) => Keyword::Label,
Self::Direction(_direction) => Keyword::Direction,
Self::Link(_link) => Keyword::Link,
Self::Tooltip(_tooltip) => Keyword::Tooltip,
Self::Style(_properties) => Keyword::Style,
Self::Svugy(_properties) => Keyword::Vars,
}
}
}
impl SvugyProperty {
pub const VARIABLE: &str = "svugy";
}
impl EdgeOperator {
pub(crate) fn from_arrow(arrow: &str) -> Self {
let has_source_arrowhead = arrow.starts_with('<');
let has_target_arrowhead = arrow.ends_with('>');
match (has_source_arrowhead, has_target_arrowhead) {
(true, true) => Self::Bidirectional,
(true, false) => Self::Backward,
(false, true) => Self::Forward,
(false, false) => Self::Undirected,
}
}
pub fn arrow(self) -> &'static str {
match self {
Self::Forward => "->",
Self::Backward => "<-",
Self::Bidirectional => "<->",
Self::Undirected => "--",
}
}
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn range(self) -> Range<usize> {
self.start..self.end
}
}
impl fmt::Debug for Span {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}..{}", self.start, self.end)
}
}
impl From<Range<usize>> for Span {
fn from(range: Range<usize>) -> Self {
Self::new(range.start, range.end)
}
}
impl From<Span> for Range<usize> {
fn from(span: Span) -> Self {
span.range()
}
}
impl From<Span> for ByteRange {
fn from(span: Span) -> Self {
ByteRange::new(span.start, span.end)
}
}
impl From<Span> for miette::SourceSpan {
fn from(span: Span) -> Self {
span.range().into()
}
}
impl chumsky::span::Span for Span {
type Context = ();
type Offset = usize;
fn new(_context: (), range: Range<usize>) -> Self {
range.into()
}
fn context(&self) {}
fn start(&self) -> usize {
self.start
}
fn end(&self) -> usize {
self.end
}
}
impl Statement {
pub fn span(&self) -> Span {
match self {
Self::Shape(shape) => shape.span,
Self::Edge(edge) => edge.span,
Self::Field(assignment) => assignment.span,
}
}
}