svugy-parse 0.1.0

Parse diagrams written in Markdown, d2 or JSON into the svugy model, and print them back
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
//! Parse d2 tokens into a syntax tree of every d2 statement, recovering from each broken one.

use chumsky::input::ValueInput;
use chumsky::prelude::*;

use super::ast::{EdgeOperator, KeySegment, Scalar, Span};
use super::diagnostic::Diagnostics;
use super::lex::Token;

/// Type the parser extra every d2 parser shares: chumsky's rich errors over d2 tokens.
type ParserExtra<'tokens, 'src> = extra::Err<Rich<'tokens, Token<'src>, Span>>;

/// Represent one d2 statement as written, before the v1 subset is applied.
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum SyntaxStatement {
    Declaration(SyntaxDeclaration),
    EdgeReference(Span),
    Spread { value: SyntaxValue, span: Span },
}

/// Hold the declaration of a key or an edge chain, with whatever is assigned to it.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SyntaxDeclaration {
    pub(crate) head: SyntaxHead,
    pub(crate) assignment: SyntaxAssignment,
    pub(crate) span: Span,
}

/// Head a declaration with what it names: one key, or an edge chain between keys.
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum SyntaxHead {
    Key(SyntaxKey),
    Edges {
        source: SyntaxKey,
        links: Vec<SyntaxLink>,
    },
}

/// Hold the assignment following a declaration's head: its `: value` and its `{ map }`, either one optional.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SyntaxAssignment {
    pub(crate) value: Option<SyntaxValue>,
    pub(crate) map: Option<Vec<SyntaxStatement>>,
}

/// Link one hop of an edge chain to its target key.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SyntaxLink {
    pub(crate) operator: EdgeOperator,
    pub(crate) operator_span: Span,
    pub(crate) target: SyntaxKey,
}

/// Spell a dotted key, flagged when it carries a glob or a `&`/`!&` filter.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SyntaxKey {
    pub(crate) is_filtered: bool,
    pub(crate) segments: Vec<SyntaxSegment>,
    pub(crate) span: Span,
}

/// Spell one key segment, flagged when it contains a `*` glob or is an unquoted `_` naming the parent scope.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SyntaxSegment {
    pub(crate) segment: KeySegment,
    pub(crate) is_glob: bool,
    pub(crate) is_parent_reference: bool,
}

/// Represent a value as written; only `Scalar` survives the v1 subset, and an `Array` of scalars only in the `svugy` carrier.
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum SyntaxValue {
    Scalar(Scalar),
    Null(Span),
    BlockString(Span),
    Import(Span),
    Substitution(Span),
    Array(SyntaxArray),
    /// Mark a `{ … }` map, which only an array holds as a value.
    Map(Span),
}

/// List the items of a `[ … ]` array as written, in order.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SyntaxArray {
    pub(crate) items: Vec<SyntaxValue>,
    pub(crate) span: Span,
}

impl SyntaxStatement {
    /// Parse every statement of a token stream, keeping the valid ones and recording one error per broken one.
    pub(crate) fn parse_all<'src>(
        tokens: &[(Token<'src>, Span)],
        source: &'src str,
        diagnostics: &mut Diagnostics,
    ) -> Vec<Self> {
        let source_length = source.len();
        let end_of_input = Span::new(source_length, source_length);
        let input = tokens.map(end_of_input, |(token, span)| (token, span));
        let document_parser = Self::document_parser(source);
        let parse_result = document_parser.parse(input);
        let (statements, rich_errors) = parse_result.into_output_errors();
        for rich_error in rich_errors {
            let message = rich_error.reason().to_string();
            let span = *rich_error.span();
            diagnostics.record_error(message, span);
        }
        statements.unwrap_or_default()
    }

    /// Parse the whole document's statement list, reporting each unmatched `}` and carrying on after it.
    fn document_parser<'tokens, 'src: 'tokens, I>(
        source: &'src str,
    ) -> impl Parser<'tokens, I, Vec<Self>, ParserExtra<'tokens, 'src>>
    where
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        let unmatched_closer = just(Token::RightBrace).validate(|_closer, extra, emitter| {
            let closer_span = extra.span();
            let error = Rich::custom(closer_span, "unmatched `}`");
            emitter.emit(error);
        });
        let end_of_input = end();
        Self::list_parser(source)
            .separated_by(unmatched_closer)
            .collect::<Vec<Vec<Self>>>()
            .map(|lists| lists.into_iter().flatten().collect())
            .then_ignore(end_of_input)
    }

    /// Parse statements up to the next `}` or the end, skipping each broken statement to its terminator.
    fn list_parser<'tokens, 'src: 'tokens, I>(
        source: &'src str,
    ) -> Boxed<'tokens, 'tokens, I, Vec<Self>, ParserExtra<'tokens, 'src>>
    where
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        recursive(|statement_list| {
            let left_brace = just(Token::LeftBrace);
            let right_brace = just(Token::RightBrace);
            let map = statement_list.delimited_by(left_brace, right_brace);
            let declaration = SyntaxDeclaration::parser(source, map);
            let reference_trees = Token::balanced_tree_parser().repeated();
            let edge_reference = just(Token::LeftParen)
                .then(reference_trees)
                .to_span()
                .map(Self::EdgeReference);
            let spread_value = SyntaxValue::parser(source);
            let spread = just(Token::Spread)
                .ignore_then(spread_value)
                .map_with(|value, extra| {
                    let span = extra.span();
                    Self::Spread { value, span }
                });
            let skipped_statement = Self::skipped_statement_parser();
            let skip_broken_statement = via_parser(skipped_statement);
            let terminator = Token::terminator_parser();
            let statement = choice((spread, edge_reference, declaration))
                .then_ignore(terminator)
                .map(Some)
                .recover_with(skip_broken_statement);
            let separator = Token::separator_parser().to(None);
            choice((separator, statement))
                .repeated()
                .collect::<Vec<Option<Self>>>()
                .map(|statements| statements.into_iter().flatten().collect())
        })
        .boxed()
    }

    /// Skip a broken statement: up to its terminator when its brackets balance, otherwise to the end of its line.
    fn skipped_statement_parser<'tokens, 'src: 'tokens, I>()
    -> impl Parser<'tokens, I, Option<Self>, ParserExtra<'tokens, 'src>> + Clone
    where
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        let terminator = Token::terminator_parser();
        let balanced_statement = Token::balanced_tree_parser()
            .repeated()
            .at_least(1)
            .then_ignore(terminator);
        let rest_of_line = none_of([Token::Newline, Token::Semicolon, Token::RightBrace])
            .repeated()
            .at_least(1);
        choice((balanced_statement, rest_of_line)).to(None)
    }
}

impl SyntaxDeclaration {
    /// Parse `head [: value] [{ map }]`, where the head is a key or an edge chain `key (op key)+`.
    fn parser<'tokens, 'src: 'tokens, I, M>(
        source: &'src str,
        map: M,
    ) -> impl Parser<'tokens, I, SyntaxStatement, ParserExtra<'tokens, 'src>> + Clone
    where
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
        M: Parser<'tokens, I, Vec<SyntaxStatement>, ParserExtra<'tokens, 'src>> + Clone,
    {
        let key = SyntaxKey::parser(source);
        let operator = select! { Token::Edge(operator) => operator }
            .labelled("edge operator")
            .map_with(|operator, extra| {
                let operator_span: Span = extra.span();
                (operator, operator_span)
            });
        let target_key = key.clone();
        let link = operator
            .then(target_key)
            .map(|((operator, operator_span), target)| SyntaxLink {
                operator,
                operator_span,
                target,
            });
        let links = link.repeated().collect::<Vec<SyntaxLink>>();
        let head = key
            .then(links)
            .map(|(source, links)| SyntaxHead::from_chain(source, links));
        let optional_value = SyntaxValue::parser(source).or_not();
        let value = just(Token::Colon).ignore_then(optional_value);
        let optional_map = map.or_not();
        let assignment = value.or_not().then(optional_map).map(|(value, map)| {
            let value = value.flatten();
            SyntaxAssignment { value, map }
        });
        head.then(assignment).map_with(|(head, assignment), extra| {
            let span = extra.span();
            let declaration = Self {
                head,
                assignment,
                span,
            };
            SyntaxStatement::Declaration(declaration)
        })
    }
}

impl SyntaxHead {
    /// Head a declaration from its parsed chain: a lone key without links, else the edge chain.
    fn from_chain(source: SyntaxKey, links: Vec<SyntaxLink>) -> Self {
        let is_single_key = links.is_empty();
        if is_single_key {
            return Self::Key(source);
        }
        Self::Edges { source, links }
    }
}

impl SyntaxKey {
    /// Parse `[&|!&] segment (. segment)*`, a segment being one quoted string or a run of unquoted pieces.
    fn parser<'tokens, 'src: 'tokens, I>(
        source: &'src str,
    ) -> Boxed<'tokens, 'tokens, I, Self, ParserExtra<'tokens, 'src>>
    where
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        let quoted_segment = select! {
            Token::DoubleQuoted(raw) => Token::decode_double_quoted(raw),
            Token::SingleQuoted(raw) => Token::decode_single_quoted(raw),
        }
        .map_with(|name, extra| {
            let span = extra.span();
            let segment = KeySegment { name, span };
            SyntaxSegment {
                segment,
                is_glob: false,
                is_parent_reference: false,
            }
        });
        let unquoted_piece_is_glob = select! {
            Token::Word => false,
            Token::Dash => false,
            Token::Dollar => false,
            Token::Pipe => false,
            Token::Star => true,
        };
        let unquoted_segment = unquoted_piece_is_glob
            .repeated()
            .at_least(1)
            .collect::<Vec<bool>>()
            .map_with(move |piece_is_glob, extra| {
                let span: Span = extra.span();
                let raw_range = span.range();
                let raw = &source[raw_range];
                let is_parent_reference = raw == "_";
                let name = Token::decode_escapes(raw);
                let segment = KeySegment { name, span };
                let is_glob = piece_is_glob.contains(&true);
                SyntaxSegment {
                    segment,
                    is_glob,
                    is_parent_reference,
                }
            });
        let segment = choice((quoted_segment, unquoted_segment));
        let ampersand = just(Token::Ampersand);
        let not_ampersand = just(Token::NotAmpersand);
        let filter = choice((ampersand, not_ampersand));
        let dot = just(Token::Dot);
        let segments = segment
            .separated_by(dot)
            .at_least(1)
            .collect::<Vec<SyntaxSegment>>();
        filter
            .or_not()
            .then(segments)
            .map_with(|(filter, segments), extra| {
                let is_filtered = filter.is_some();
                let span = extra.span();
                Self {
                    is_filtered,
                    segments,
                    span,
                }
            })
            .labelled("key")
            .boxed()
    }
}

impl SyntaxValue {
    /// Parse a value up to the next separator, brace or bracket, or one `[ … ]` array of values and maps split by separators.
    fn parser<'tokens, 'src: 'tokens, I>(
        source: &'src str,
    ) -> Boxed<'tokens, 'tokens, I, Self, ParserExtra<'tokens, 'src>>
    where
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        recursive(|value| {
            let separator = Token::separator_parser().to(None);
            let braced_tree = Token::balanced_tree_parser();
            let map = just(Token::LeftBrace)
                .rewind()
                .ignore_then(braced_tree)
                .to_span()
                .map(Self::Map);
            let item = choice((map, value)).map(Some);
            let left_bracket = just(Token::LeftBracket);
            let right_bracket = just(Token::RightBracket);
            let array = choice((item, separator))
                .repeated()
                .collect::<Vec<Option<Self>>>()
                .delimited_by(left_bracket, right_bracket)
                .map_with(|items, extra| {
                    let items = items.into_iter().flatten().collect();
                    let span = extra.span();
                    let array = SyntaxArray { items, span };
                    Self::Array(array)
                });
            let run = none_of(Token::SEPARATORS_AND_BRACKETS)
                .repeated()
                .at_least(1)
                .collect::<Vec<Token<'src>>>()
                .map_with(move |pieces, extra| {
                    let span = extra.span();
                    Self::from_run(&pieces, span, source)
                });
            choice((array, run)).labelled("value")
        })
        .boxed()
    }

    /// Read a run of value tokens: a lone quoted string or block string, an `@` import, a `${…}` substitution, else unquoted text.
    fn from_run(pieces: &[Token<'_>], span: Span, source: &str) -> Self {
        match pieces {
            [Token::DoubleQuoted(raw)] => {
                let text = Token::decode_double_quoted(raw);
                let scalar = Scalar { text, span };
                Self::Scalar(scalar)
            }
            [Token::SingleQuoted(raw)] => {
                let text = Token::decode_single_quoted(raw);
                let scalar = Scalar { text, span };
                Self::Scalar(scalar)
            }
            [Token::BlockString] => Self::BlockString(span),
            [Token::At, ..] => Self::Import(span),
            substituted_pieces if substituted_pieces.contains(&Token::Substitution) => {
                Self::Substitution(span)
            }
            _unquoted_pieces => {
                let raw_range = span.range();
                let raw = &source[raw_range];
                if raw == "null" {
                    return Self::Null(span);
                }
                let text = Token::decode_escapes(raw);
                let scalar = Scalar { text, span };
                Self::Scalar(scalar)
            }
        }
    }
}

impl<'src> Token<'src> {
    /// List the tokens that end a value: the two separators and every bracket.
    const SEPARATORS_AND_BRACKETS: [Self; 6] = [
        Self::Newline,
        Self::Semicolon,
        Self::LeftBrace,
        Self::RightBrace,
        Self::LeftBracket,
        Self::RightBracket,
    ];

    /// Match one statement separator: a newline or a `;`.
    fn separator_parser<'tokens, I>()
    -> impl Parser<'tokens, I, Token<'src>, ParserExtra<'tokens, 'src>> + Clone
    where
        'src: 'tokens,
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        let newline = just(Token::Newline);
        let semicolon = just(Token::Semicolon);
        choice((newline, semicolon))
    }

    /// Match one balanced tree: a token outside brackets, or a whole `{ … }` / `[ … ]` group, separators inside it included.
    fn balanced_tree_parser<'tokens, I>()
    -> impl Parser<'tokens, I, (), ParserExtra<'tokens, 'src>> + Clone
    where
        'src: 'tokens,
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        recursive(|tree| {
            let separator = Self::separator_parser().ignored();
            let contents = choice((tree, separator)).repeated();
            let left_brace = just(Token::LeftBrace);
            let right_brace = just(Token::RightBrace);
            let braced = contents.clone().delimited_by(left_brace, right_brace);
            let left_bracket = just(Token::LeftBracket);
            let right_bracket = just(Token::RightBracket);
            let bracketed = contents.delimited_by(left_bracket, right_bracket);
            let plain = none_of(Self::SEPARATORS_AND_BRACKETS).ignored();
            choice((braced, bracketed, plain))
        })
    }

    /// Look ahead, without consuming, for a statement terminator: a separator, a `}` or the end.
    fn terminator_parser<'tokens, I>()
    -> impl Parser<'tokens, I, (), ParserExtra<'tokens, 'src>> + Clone
    where
        'src: 'tokens,
        I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
    {
        let separator = Self::separator_parser();
        let right_brace = just(Token::RightBrace);
        let closing_token = choice((separator, right_brace)).ignored();
        let end_of_input = end();
        choice((closing_token, end_of_input))
            .labelled("end of statement")
            .rewind()
    }
}

impl SyntaxValue {
    /// Return the span of the value as written.
    pub(crate) fn span(&self) -> Span {
        match self {
            Self::Scalar(scalar) => scalar.span,
            Self::Array(array) => array.span,
            Self::Null(span)
            | Self::BlockString(span)
            | Self::Import(span)
            | Self::Substitution(span)
            | Self::Map(span) => *span,
        }
    }
}

impl SyntaxStatement {
    /// Return the span of the statement as written.
    pub(crate) fn span(&self) -> Span {
        match self {
            Self::Declaration(declaration) => declaration.span,
            Self::EdgeReference(span) => *span,
            Self::Spread {
                value: _value,
                span,
            } => *span,
        }
    }
}