1use 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#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct D2Language;
15
16pub type SyntaxError = SourceError<D2Language>;
18
19#[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#[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
55pub(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 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}