Skip to main content

svugy_parse/d2/
diagnostic.rs

1//! Report what the d2 importer rejected (errors) and what it ignored as outside the v1 subset or the model (warnings).
2
3use std::fmt;
4
5use miette::{Diagnostic, SourceSpan};
6use thiserror::Error;
7
8use super::ast::{EdgeOperator, Span};
9use super::{D2Import, D2Parse};
10use crate::diagnostic::{SourceError, SourceLanguage};
11
12/// Mark d2 as a source language: its errors are syntax errors, its warnings the constructs outside the v1 subset.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct D2Language;
15
16/// Report a syntax error: source text d2 cannot read or compile, whose statement is dropped.
17pub type SyntaxError = SourceError<D2Language>;
18
19/// Ignore a construct d2 accepts but the v1 subset or the model does not hold.
20#[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic)]
21#[error("{construct} is outside svugy's d2 subset and is ignored")]
22#[diagnostic(code(svugy::d2::unsupported), severity(Warning))]
23pub struct UnsupportedConstruct {
24    pub construct: Construct,
25    #[label("ignored")]
26    pub span: SourceSpan,
27}
28
29/// Name the d2 construct that fell outside the v1 subset.
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum Construct {
32    Variables,
33    Classes,
34    Board(&'static str),
35    Import,
36    Glob,
37    EdgeReference,
38    SpecialShape(String),
39    BlockString,
40    Null,
41    Array,
42    ArrayMap,
43    ReservedField(&'static str),
44    FieldForm(String),
45    DeclarationInEdgeMap,
46    UnmappedShape(String),
47    UnmappedDirection(String),
48    ArrowheadForm(EdgeOperator),
49    FieldOnEdge(&'static str),
50    FieldOnRoot(&'static str),
51    UnknownStyleKey(String),
52    ContainerBadge,
53}
54
55/// Record the diagnostics of d2's stages, handed from one stage to the next.
56pub(crate) type Diagnostics = crate::diagnostic::Diagnostics<D2Language>;
57
58impl SourceLanguage for D2Language {
59    const ERROR_CODE: &'static str = "svugy::d2::syntax";
60    type Warning = UnsupportedConstruct;
61
62    fn import(source_text: &str) -> D2Import {
63        let parsed = D2Parse::parse(source_text);
64        parsed.lower()
65    }
66}
67
68impl Diagnostics {
69    /// Record a warning that `construct` at `span` is ignored.
70    pub(crate) fn record_warning(&mut self, construct: Construct, span: Span) {
71        let span = SourceSpan::from(span);
72        let warning = UnsupportedConstruct { construct, span };
73        self.warnings.push(warning);
74    }
75}
76
77impl fmt::Display for Construct {
78    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            Self::Variables => write!(formatter, "a variable (`vars`, `${{…}}`)"),
81            Self::Classes => write!(formatter, "a class (`classes`, `class`)"),
82            Self::Board(keyword) => write!(formatter, "a board (`{keyword}`)"),
83            Self::Import => write!(formatter, "an import (`@…`)"),
84            Self::Glob => write!(formatter, "a glob or filter (`*`, `&`, `!&`)"),
85            Self::EdgeReference => write!(formatter, "an edge reference (`(a -> b)[0]`)"),
86            Self::SpecialShape(shape) => write!(formatter, "the special shape `{shape}`"),
87            Self::BlockString => write!(formatter, "a block string (`|md … |`)"),
88            Self::Null => write!(formatter, "a `null` deletion"),
89            Self::Array => write!(formatter, "an array value (`[…]`)"),
90            Self::ArrayMap => write!(formatter, "a map inside an array (`[{{…}}]`)"),
91            Self::ReservedField(keyword) => write!(formatter, "the field `{keyword}`"),
92            Self::FieldForm(keyword) => write!(formatter, "this form of the field `{keyword}`"),
93            Self::DeclarationInEdgeMap => write!(formatter, "a declaration inside an edge map"),
94            Self::UnmappedShape(shape) => write!(formatter, "the shape `{shape}`"),
95            Self::UnmappedDirection(direction) => write!(formatter, "`direction: {direction}`"),
96            Self::ArrowheadForm(operator) => {
97                let arrow = operator.arrow();
98                write!(formatter, "the arrowheads of `{arrow}`")
99            }
100            Self::FieldOnEdge(keyword) => write!(formatter, "the field `{keyword}` on an edge"),
101            Self::FieldOnRoot(keyword) => write!(formatter, "the field `{keyword}` on the root"),
102            Self::UnknownStyleKey(key) => write!(formatter, "the style key `{key}`"),
103            Self::ContainerBadge => write!(formatter, "`svugy.badge` on a container"),
104        }
105    }
106}