1use std::fmt;
4use std::ops::Range;
5
6use strum::{EnumString, IntoStaticStr};
7use svugy_model::model::ByteRange;
8
9#[derive(Debug, Clone, PartialEq, Default)]
11pub struct D2Document {
12 pub statements: Vec<Statement>,
13}
14
15#[derive(Debug, Clone, PartialEq)]
17pub enum Statement {
18 Shape(ShapeDeclaration),
19 Edge(EdgeDeclaration),
20 Field(FieldAssignment),
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct ShapeDeclaration {
26 pub path: KeyPath,
27 pub label: Option<Scalar>,
28 pub body: Vec<Statement>,
29 pub span: Span,
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub struct EdgeDeclaration {
35 pub source: KeyPath,
36 pub links: Vec<EdgeLink>,
37 pub label: Option<Scalar>,
38 pub fields: Vec<Field>,
39 pub span: Span,
40}
41
42#[derive(Debug, Clone, PartialEq)]
44pub struct EdgeLink {
45 pub operator: EdgeOperator,
46 pub operator_span: Span,
47 pub target: KeyPath,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum EdgeOperator {
53 Forward,
54 Backward,
55 Bidirectional,
56 Undirected,
57}
58
59#[derive(Debug, Clone, PartialEq)]
61pub struct FieldAssignment {
62 pub target: KeyPath,
63 pub field: Field,
64 pub span: Span,
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub enum Field {
70 Shape(Scalar),
71 Label(Scalar),
72 Direction(Scalar),
73 Link(Scalar),
74 Tooltip(Scalar),
75 Style(Vec<StyleProperty>),
76 Svugy(Vec<SvugyProperty>),
77}
78
79#[derive(Debug, Clone, PartialEq)]
81pub struct StyleProperty {
82 pub name: KeySegment,
83 pub value: Scalar,
84 pub span: Span,
85}
86
87#[derive(Debug, Clone, PartialEq)]
89pub struct SvugyProperty {
90 pub attribute_path: Vec<String>,
91 pub value: Scalar,
92 pub span: Span,
93}
94
95#[derive(Debug, Clone, PartialEq, Default)]
97pub struct KeyPath {
98 pub parent_references: usize,
99 pub segments: Vec<KeySegment>,
100 pub span: Span,
101}
102
103#[derive(Debug, Clone, PartialEq)]
105pub struct KeySegment {
106 pub name: String,
107 pub span: Span,
108}
109
110#[derive(Debug, Clone, PartialEq)]
112pub struct Scalar {
113 pub text: String,
114 pub span: Span,
115}
116
117#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
119pub struct Span {
120 pub start: usize,
121 pub end: usize,
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, IntoStaticStr)]
126#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
127pub(crate) enum Keyword {
128 Shape,
129 Label,
130 Direction,
131 Link,
132 Style,
133 Vars,
134 Class,
135 Classes,
136 Layers,
137 Scenarios,
138 Steps,
139 Icon,
140 Constraint,
141 Tooltip,
142 Near,
143 Width,
144 Height,
145 Top,
146 Left,
147 GridRows,
148 GridColumns,
149 GridGap,
150 VerticalGap,
151 HorizontalGap,
152 SourceArrowhead,
153 TargetArrowhead,
154}
155
156impl Field {
157 pub(crate) fn keyword(&self) -> Keyword {
159 match self {
160 Self::Shape(_shape) => Keyword::Shape,
161 Self::Label(_label) => Keyword::Label,
162 Self::Direction(_direction) => Keyword::Direction,
163 Self::Link(_link) => Keyword::Link,
164 Self::Tooltip(_tooltip) => Keyword::Tooltip,
165 Self::Style(_properties) => Keyword::Style,
166 Self::Svugy(_properties) => Keyword::Vars,
167 }
168 }
169}
170
171impl SvugyProperty {
172 pub const VARIABLE: &str = "svugy";
174}
175
176impl EdgeOperator {
177 pub(crate) fn from_arrow(arrow: &str) -> Self {
179 let has_source_arrowhead = arrow.starts_with('<');
180 let has_target_arrowhead = arrow.ends_with('>');
181 match (has_source_arrowhead, has_target_arrowhead) {
182 (true, true) => Self::Bidirectional,
183 (true, false) => Self::Backward,
184 (false, true) => Self::Forward,
185 (false, false) => Self::Undirected,
186 }
187 }
188
189 pub fn arrow(self) -> &'static str {
191 match self {
192 Self::Forward => "->",
193 Self::Backward => "<-",
194 Self::Bidirectional => "<->",
195 Self::Undirected => "--",
196 }
197 }
198}
199
200impl Span {
201 pub fn new(start: usize, end: usize) -> Self {
203 Self { start, end }
204 }
205
206 pub fn range(self) -> Range<usize> {
208 self.start..self.end
209 }
210}
211
212impl fmt::Debug for Span {
213 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
214 write!(formatter, "{}..{}", self.start, self.end)
215 }
216}
217
218impl From<Range<usize>> for Span {
219 fn from(range: Range<usize>) -> Self {
220 Self::new(range.start, range.end)
221 }
222}
223
224impl From<Span> for Range<usize> {
225 fn from(span: Span) -> Self {
226 span.range()
227 }
228}
229
230impl From<Span> for ByteRange {
231 fn from(span: Span) -> Self {
232 ByteRange::new(span.start, span.end)
233 }
234}
235
236impl From<Span> for miette::SourceSpan {
237 fn from(span: Span) -> Self {
238 span.range().into()
239 }
240}
241
242impl chumsky::span::Span for Span {
243 type Context = ();
244 type Offset = usize;
245
246 fn new(_context: (), range: Range<usize>) -> Self {
247 range.into()
248 }
249
250 fn context(&self) {}
251
252 fn start(&self) -> usize {
253 self.start
254 }
255
256 fn end(&self) -> usize {
257 self.end
258 }
259}
260
261impl Statement {
262 pub fn span(&self) -> Span {
264 match self {
265 Self::Shape(shape) => shape.span,
266 Self::Edge(edge) => edge.span,
267 Self::Field(assignment) => assignment.span,
268 }
269 }
270}