ferrishot_knus/
grammar.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use chumsky::prelude::*;
4
5use crate::ast::{Decimal, Integer, Literal, Node, Radix, TypeName, Value};
6use crate::ast::{Document, SpannedName, SpannedNode};
7use crate::errors::{ParseError as Error, TokenFormat};
8use crate::span::Spanned;
9use crate::traits::Span;
10
11use chumsky::chain::Chain;
12use chumsky::combinator::{Map, Then};
13
14type MapChar<O, U> = fn(_: (O, U)) -> Vec<char>;
15
16trait ChainChar<I: Clone, O> {
17    type Error;
18    fn chain_c<U, P>(self, other: P) -> Map<Then<Self, P>, MapChar<O, U>, (O, U)>
19    where
20        Self: Sized,
21        U: Chain<char>,
22        O: Chain<char>,
23        P: Parser<I, U, Error = Self::Error>;
24}
25
26impl<I: Clone, O, R: Parser<I, O>> ChainChar<I, O> for R {
27    type Error = <R as Parser<I, O>>::Error;
28    fn chain_c<U, P>(self, other: P) -> Map<Then<Self, P>, MapChar<O, U>, (O, U)>
29    where
30        Self: Sized,
31        U: Chain<char>,
32        O: Chain<char>,
33        P: Parser<I, U, Error = Self::Error>,
34    {
35        Parser::chain(self, other)
36    }
37}
38
39fn begin_comment<S: Span>(which: char) -> impl Parser<char, (), Error = Error<S>> + Clone {
40    just('/')
41        .map_err(|e: Error<S>| e.with_no_expected())
42        .ignore_then(just(which).ignored())
43}
44
45fn newline<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
46    just('\r')
47        .or_not()
48        .ignore_then(just('\n'))
49        .or(just('\r')) // Carriage return
50        .or(just('\x0C')) // Form feed
51        .or(just('\x0B')) // Vertical tab
52        .or(just('\u{0085}')) // Next line
53        .or(just('\u{2028}')) // Line separator
54        .or(just('\u{2029}')) // Paragraph separator
55        .ignored()
56        .map_err(|e: Error<S>| e.with_expected_kind("newline"))
57}
58
59fn ws_char<S: Span>() -> impl Parser<char, (), Error = Error<S>> + Clone {
60    filter(|c| {
61        matches!(
62            c,
63            '\t' | ' ' | '\u{00a0}' | '\u{1680}' | '\u{2000}'
64                ..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}'
65        )
66    })
67    .ignored()
68}
69
70fn id_char<S: Span>() -> impl Parser<char, char, Error = Error<S>> {
71    filter(|c| {
72        !matches!(c,
73            '\u{0000}'..='\u{0021}' |
74            '\\'|'/'|'('|')'|'{'|'}'|';'|'['|']'|'='|'"'|'#' |
75            // whitespace, excluding 0x20
76            '\u{00a0}' | '\u{1680}' |
77            '\u{2000}'..='\u{200A}' |
78            '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
79            // newline (excluding <= 0x20)
80            '\u{0085}' | '\u{2028}' | '\u{2029}'
81        )
82    })
83    .map_err(|e: Error<S>| e.with_expected_kind("letter"))
84}
85
86fn id_sans_dig<S: Span>() -> impl Parser<char, char, Error = Error<S>> {
87    filter(|c| {
88        !matches!(c,
89            '0'..='9' |
90            '\u{0000}'..='\u{0020}' |
91            '\\'|'/'|'('|')'|'{'|'}'|';'|'['|']'|'='|'"'|'#' |
92            // whitespace, excluding 0x20
93            '\u{00a0}' | '\u{1680}' |
94            '\u{2000}'..='\u{200A}' |
95            '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
96            // newline (excluding <= 0x20)
97            '\u{0085}' | '\u{2028}' | '\u{2029}'
98        )
99    })
100    .map_err(|e: Error<S>| e.with_expected_kind("letter"))
101}
102
103fn id_sans_dig_point<S: Span>() -> impl Parser<char, char, Error = Error<S>> {
104    filter(|c| {
105        !matches!(c,
106            '0'..='9' | '.' |
107            '\u{0000}'..='\u{0020}' |
108            '\\'|'/'|'('|')'|'{'|'}'|';'|'['|']'|'='|'"'|'#' |
109            // whitespace, excluding 0x20
110            '\u{00a0}' | '\u{1680}' |
111            '\u{2000}'..='\u{200A}' |
112            '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
113            // newline (excluding <= 0x20)
114            '\u{0085}' | '\u{2028}' | '\u{2029}'
115        )
116    })
117    .map_err(|e: Error<S>| e.with_expected_kind("letter"))
118}
119
120fn id_sans_sign_dig_point<S: Span>() -> impl Parser<char, char, Error = Error<S>> {
121    filter(|c| {
122        !matches!(c,
123            '-'| '+' | '0'..='9' |
124            '\u{0000}'..='\u{0020}' |
125            '\\'|'/'|'('|')'|'{'|'}'|';'|'['|']'|'='|'"'|'#' |
126            // whitespace, excluding 0x20
127            '\u{00a0}' | '\u{1680}' |
128            '\u{2000}'..='\u{200A}' |
129            '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
130            // newline (excluding <= 0x20)
131            '\u{0085}' | '\u{2028}' | '\u{2029}'
132        )
133    })
134    .map_err(|e: Error<S>| e.with_expected_kind("letter"))
135}
136
137fn ws<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
138    ws_char()
139        .repeated()
140        .at_least(1)
141        .ignored()
142        .or(ml_comment())
143        .map_err(|e| e.with_expected_kind("whitespace"))
144}
145
146fn comment<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
147    begin_comment('/')
148        .then(take_until(newline().or(end())))
149        .ignored()
150}
151
152fn ml_comment<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
153    recursive::<_, _, _, _, Error<S>>(|comment| {
154        choice((
155            comment,
156            none_of('*').ignored(),
157            just('*').then_ignore(none_of('/').rewind()).ignored(),
158        ))
159        .repeated()
160        .ignored()
161        .delimited_by(begin_comment('*'), just("*/"))
162    })
163    .map_err_with_span(|e, span| {
164        if matches!(
165            &e,
166            Error::Unexpected {
167                found: TokenFormat::Eoi,
168                ..
169            }
170        ) && span.length() > 2
171        {
172            e.merge(Error::Unclosed {
173                label: "comment",
174                opened_at: span.at_start(2),
175                opened: "/*".into(),
176                expected_at: span.at_end(),
177                expected: "*/".into(),
178                found: None.into(),
179            })
180        } else {
181            // otherwise opening /* is not matched
182            e
183        }
184    })
185}
186
187fn raw_string<S: Span>() -> impl Parser<char, Box<str>, Error = Error<S>> {
188     just('#').repeated().at_least(1).map(|v| v.len())
189        .then_ignore(just('"'))
190        .then_with(|sharp_num| {
191            take_until(just('"').ignore_then(just('#').repeated().exactly(sharp_num).ignored()))
192                .map_err_with_span(move |e: Error<S>, span| {
193                    if matches!(
194                        &e,
195                        Error::Unexpected {
196                            found: TokenFormat::Eoi,
197                            ..
198                        }
199                    ) {
200                        e.merge(Error::Unclosed {
201                            label: "raw string",
202                            opened_at: span.before_start(sharp_num + 2),
203                            opened: TokenFormat::OpenRaw(sharp_num),
204                            expected_at: span.at_end(),
205                            expected: TokenFormat::CloseRaw(sharp_num),
206                            found: None.into(),
207                        })
208                    } else {
209                        e
210                    }
211                })
212        })
213        .map(|(text, ())| text.into_iter().collect::<String>().into())
214}
215
216fn string<S: Span>() -> impl Parser<char, Box<str>, Error = Error<S>> {
217    raw_string().or(escaped_string())
218}
219
220fn expected_kind(s: &'static str) -> BTreeSet<TokenFormat> {
221    [TokenFormat::Kind(s)].into_iter().collect()
222}
223
224fn esc_char<S: Span>() -> impl Parser<char, char, Error = Error<S>> {
225    filter_map(|span, c| match c {
226        '"' | '\\' => Ok(c),
227        'b' => Ok('\u{0008}'),
228        'f' => Ok('\u{000C}'),
229        'n' => Ok('\n'),
230        'r' => Ok('\r'),
231        't' => Ok('\t'),
232        's' => Ok(' '),
233        c => Err(Error::Unexpected {
234            label: Some("invalid escape char"),
235            span,
236            found: c.into(),
237            expected: "\"\\bfnrts".chars().map(|c| c.into()).collect(),
238        }),
239    })
240    .or(just('u').ignore_then(
241        filter_map(|span, c: char| {
242            c.is_ascii_hexdigit()
243                .then_some(c)
244                .ok_or_else(|| Error::Unexpected {
245                    label: Some("unexpected character"),
246                    span,
247                    found: c.into(),
248                    expected: expected_kind("hexadecimal digit"),
249                })
250        })
251        .repeated()
252        .at_least(1)
253        .at_most(6)
254        .delimited_by(just('{'), just('}'))
255        .try_map(|hex_chars, span| {
256            let s = hex_chars.into_iter().collect::<String>();
257            let c = u32::from_str_radix(&s, 16)
258                .map_err(|e| e.to_string())
259                .and_then(|n| char::try_from(n).map_err(|e| e.to_string()))
260                .map_err(|e| Error::Message {
261                    label: Some("invalid character code"),
262                    span,
263                    message: e.to_string(),
264                })?;
265            Ok(c)
266        })
267        .recover_with(skip_until(['}', '"', '\\'], |_| '\0')),
268    ))
269}
270
271fn escaped_string<S: Span>() -> impl Parser<char, Box<str>, Error = Error<S>> {
272    just('"').ignore_then(
273        choice((
274            filter(|&c| c != '"' && c != '\\'),
275            just('\\').ignore_then(esc_char()),
276            // ws-escape
277            just('\\').then(ws_char().or(newline()).repeated().at_least(1)).map(|_| ' '),
278        ))
279            .repeated()
280            .then_ignore(just('"'))
281            .map(|val| val.into_iter().collect::<String>().into())
282            .map_err_with_span(|e: Error<S>, span| {
283                if matches!(
284                    &e,
285                    Error::Unexpected {
286                        found: TokenFormat::Eoi,
287                        ..
288                    }
289                ) {
290                    e.merge(Error::Unclosed {
291                        label: "string",
292                        opened_at: span.before_start(1),
293                        opened: '"'.into(),
294                        expected_at: span.at_end(),
295                        expected: '"'.into(),
296                        found: None.into(),
297                    })
298                } else {
299                    e
300                }
301            }),
302    )
303}
304
305fn bare_ident<S: Span>() -> impl Parser<char, Box<str>, Error = Error<S>> {
306    let sign = just('+').or(just('-'));
307    choice((
308        // unambiguous-ident
309        id_sans_sign_dig_point().chain(id_char().repeated()),
310        // signed-ident
311        sign.chain(id_sans_dig_point().chain(id_char().repeated()).or_not()),
312        // dotted-ident
313        sign.or_not().chain(just('.')).chain(id_sans_dig().chain(id_char().repeated()).or_not()),
314    ))
315    .map(|v| v.into_iter().collect())
316    .try_map(|s: String, span| match &s[..] {
317        "true" | "false" | "null" | "nan" | "inf" | "-inf" => Err(Error::Message {
318            label: Some("illegal identifier"),
319            span,
320            message: format!("`{s}` is not allowed as a bare string"),
321        }),
322        "#true" => Err(Error::Unexpected {
323            label: Some("keyword"),
324            span,
325            found: TokenFormat::Token("#true"),
326            expected: expected_kind("identifier"),
327        }),
328        "#false" => Err(Error::Unexpected {
329            label: Some("keyword"),
330            span,
331            found: TokenFormat::Token("#false"),
332            expected: expected_kind("identifier"),
333        }),
334        "#null" => Err(Error::Unexpected {
335            label: Some("keyword"),
336            span,
337            found: TokenFormat::Token("#null"),
338            expected: expected_kind("identifier"),
339        }),
340        "#nan" => Err(Error::Unexpected {
341            label: Some("keyword"),
342            span,
343            found: TokenFormat::Token("#nan"),
344            expected: expected_kind("identifier"),
345        }),
346        "#inf" => Err(Error::Unexpected {
347            label: Some("keyword"),
348            span,
349            found: TokenFormat::Token("#inf"),
350            expected: expected_kind("identifier"),
351        }),
352        "#-inf" => Err(Error::Unexpected {
353            label: Some("keyword"),
354            span,
355            found: TokenFormat::Token("#-inf"),
356            expected: expected_kind("identifier"),
357        }),
358        _ => Ok(s.into()),
359    })
360}
361
362fn ident<S: Span>() -> impl Parser<char, Box<str>, Error = Error<S>> {
363    choice((
364        // match -123 so `-` will not be treated as an ident by backtracking
365        number().map(Err),
366        bare_ident().map(Ok),
367        string().map(Ok),
368    ))
369    // when backtracking is not already possible,
370    // throw error for numbers (mapped to `Result::Err`)
371    .try_map(|res, span| {
372        res.map_err(|_| Error::Unexpected {
373            label: Some("unexpected number"),
374            span,
375            found: TokenFormat::Kind("number"),
376            expected: expected_kind("identifier"),
377        })
378    })
379}
380
381fn keyword<S: Span>() -> impl Parser<char, Literal, Error = Error<S>> {
382    choice((
383        just("#null")
384            .map_err(|e: Error<S>| e.with_expected_token("#null"))
385            .to(Literal::Null),
386        just("#true")
387            .map_err(|e: Error<S>| e.with_expected_token("#true"))
388            .to(Literal::Bool(true)),
389        just("#false")
390            .map_err(|e: Error<S>| e.with_expected_token("#false"))
391            .to(Literal::Bool(false)),
392        just("#nan")
393            .map_err(|e: Error<S>| e.with_expected_token("#nan"))
394            .to(Literal::Nan),
395        just("#inf")
396            .map_err(|e: Error<S>| e.with_expected_token("#inf"))
397            .to(Literal::Inf),
398        just("#-inf")
399            .map_err(|e: Error<S>| e.with_expected_token("#-inf"))
400            .to(Literal::NegInf),
401    ))
402}
403
404fn digit<S: Span>(radix: u32) -> impl Parser<char, char, Error = Error<S>> {
405    filter(move |c: &char| c.is_digit(radix))
406}
407
408fn digits<S: Span>(radix: u32) -> impl Parser<char, Vec<char>, Error = Error<S>> {
409    filter(move |c: &char| c == &'_' || c.is_digit(radix)).repeated()
410}
411
412fn decimal_number<S: Span>() -> impl Parser<char, Literal, Error = Error<S>> {
413    just('-')
414        .or(just('+'))
415        .or_not()
416        .chain_c(digit(10))
417        .chain_c(digits(10))
418        .chain_c(
419            just('.')
420                .chain_c(digit(10))
421                .chain_c(digits(10))
422                .or_not()
423                .flatten(),
424        )
425        .chain_c(
426            just('e')
427                .or(just('E'))
428                .chain_c(just('-').or(just('+')).or_not())
429                .chain_c(digits(10))
430                .or_not()
431                .flatten(),
432        )
433        .map(|v| {
434            let is_decimal = v.iter().any(|c| matches!(c, '.' | 'e' | 'E'));
435            let s: String = v.into_iter().filter(|c| c != &'_').collect();
436            if is_decimal {
437                Literal::Decimal(Decimal(s.into()))
438            } else {
439                Literal::Int(Integer(Radix::Dec, s.into()))
440            }
441        })
442}
443
444fn radix_number<S: Span>() -> impl Parser<char, Literal, Error = Error<S>> {
445    just('-')
446        .or(just('+'))
447        .or_not()
448        .then_ignore(just('0'))
449        .then(choice((
450            just('b').ignore_then(digit(2).chain(digits(2)).map(|s| (Radix::Bin, s))),
451            just('o').ignore_then(digit(8).chain(digits(8)).map(|s| (Radix::Oct, s))),
452            just('x').ignore_then(digit(16).chain(digits(16)).map(|s| (Radix::Hex, s))),
453        )))
454        .map(|(sign, (radix, value))| {
455            let mut s = String::with_capacity(value.len() + sign.map_or(0, |_| 1));
456            if let Some(c) = sign {
457                s.push(c);
458            }
459            s.extend(value.into_iter().filter(|&c| c != '_'));
460            Literal::Int(Integer(radix, s.into()))
461        })
462}
463
464fn number<S: Span>() -> impl Parser<char, Literal, Error = Error<S>> {
465    radix_number().or(decimal_number())
466}
467
468fn literal<S: Span>() -> impl Parser<char, Literal, Error = Error<S>> {
469    choice((ident().map(Literal::String), keyword(), number()))
470}
471
472fn type_name<S: Span>() -> impl Parser<char, TypeName, Error = Error<S>> {
473    ident()
474        .delimited_by(
475            just('(').then(ws_char().repeated()),
476            ws_char().repeated().then(just(')'))
477        )
478        .map(TypeName::from_string)
479}
480
481fn spanned<T, S, P>(p: P) -> impl Parser<char, Spanned<T, S>, Error = Error<S>>
482where
483    P: Parser<char, T, Error = Error<S>>,
484    S: Span,
485{
486    p.map_with_span(|value, span| Spanned { span, value })
487}
488
489fn esc_line<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
490    just('\\')
491        .ignore_then(ws().repeated())
492        .ignore_then(comment().or(newline()).or(end()))
493}
494
495fn node_space<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
496    ws().or(esc_line())
497}
498
499fn node_terminator<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
500    choice((newline(), comment(), just(';').ignored(), end()))
501}
502
503enum PropOrArg<S> {
504    Prop(SpannedName<S>, Value<S>),
505    Arg(Value<S>),
506    Ignore,
507}
508
509fn type_name_value<S: Span>() -> impl Parser<char, Value<S>, Error = Error<S>> {
510    spanned(type_name().then_ignore(ws_char().repeated()))
511        .then(spanned(literal()))
512        .map(|(type_name, literal)| Value {
513            type_name: Some(type_name),
514            literal,
515        })
516}
517
518fn value<S: Span>() -> impl Parser<char, Value<S>, Error = Error<S>> {
519    type_name_value().or(spanned(literal()).map(|literal| Value {
520        type_name: None,
521        literal,
522    }))
523}
524
525fn prop_or_arg_inner<S: Span>() -> impl Parser<char, PropOrArg<S>, Error = Error<S>> {
526    use PropOrArg::*;
527    choice((
528        spanned(literal())
529            .then(
530                ws_char().repeated()
531                    .then(just('='))
532                    .then(ws_char().repeated())
533                    .ignore_then(value()).or_not()
534            )
535            .try_map(|(name, value), _| {
536                let name_span = name.span;
537                match (name.value, value) {
538                    (Literal::String(s), Some(value)) => {
539                        let name = Spanned {
540                            span: name_span,
541                            value: s,
542                        };
543                        Ok(Prop(name, value))
544                    }
545                    (Literal::Bool(_) | Literal::Null | Literal::Nan | Literal::Inf | Literal::NegInf, Some(_)) => Err(Error::Unexpected {
546                        label: Some("unexpected keyword"),
547                        span: name_span,
548                        found: TokenFormat::Kind("keyword"),
549                        expected: [TokenFormat::Kind("identifier"), TokenFormat::Kind("string")]
550                            .into_iter()
551                            .collect(),
552                    }),
553                    (Literal::Int(_) | Literal::Decimal(_), Some(_)) => {
554                        Err(Error::MessageWithHelp {
555                            label: Some("unexpected number"),
556                            span: name_span,
557                            message: "numbers cannot be used as property names".into(),
558                            help: "consider enclosing in double quotes \"..\"",
559                        })
560                    }
561                    (value, None) => Ok(Arg(Value {
562                        type_name: None,
563                        literal: Spanned {
564                            span: name_span,
565                            value,
566                        },
567                    })),
568                }
569            }),
570        spanned(bare_ident())
571            .then(
572                ws_char().repeated()
573                    .then(just('='))
574                    .then(ws_char().repeated())
575                    .ignore_then(value()).or_not()
576            )
577            .validate(|(name, value), span, emit| {
578                if value.is_none() {
579                    emit(Error::MessageWithHelp {
580                        label: Some("unexpected identifier"),
581                        span,
582                        message: "identifiers cannot be used as arguments".into(),
583                        help: "consider enclosing in double quotes \"..\"",
584                    });
585                }
586                (name, value)
587            })
588            .map(|(name, value)| {
589                if let Some(value) = value {
590                    Prop(name, value)
591                } else {
592                    // this is invalid, but we already emitted error
593                    // in validate() above, so doing a sane fallback
594                    Arg(Value {
595                        type_name: None,
596                        literal: name.map(Literal::String),
597                    })
598                }
599            }),
600        type_name_value().map(Arg),
601    ))
602}
603
604fn prop_or_arg<S: Span>() -> impl Parser<char, PropOrArg<S>, Error = Error<S>> {
605    begin_comment('-')
606        .ignore_then(line_space().repeated())
607        .ignore_then(prop_or_arg_inner())
608        .map(|_| PropOrArg::Ignore)
609        .or(prop_or_arg_inner())
610}
611
612fn line_space<S: Span>() -> impl Parser<char, (), Error = Error<S>> {
613    newline().or(ws()).or(comment())
614}
615
616fn nodes<S: Span>() -> impl Parser<char, Vec<SpannedNode<S>>, Error = Error<S>> {
617    use PropOrArg::*;
618    recursive(|nodes: chumsky::recursive::Recursive<char, _, Error<S>>| {
619        let braced_nodes =
620            just('{').ignore_then(nodes.then_ignore(just('}')).map_err_with_span(|e, span| {
621                if matches!(
622                    &e,
623                    Error::Unexpected {
624                        found: TokenFormat::Eoi,
625                        ..
626                    }
627                ) {
628                    e.merge(Error::Unclosed {
629                        label: "curly braces",
630                        // we know it's `{` at the start of the span
631                        opened_at: span.before_start(1),
632                        opened: '{'.into(),
633                        expected_at: span.at_end(),
634                        expected: '}'.into(),
635                        found: None.into(),
636                    })
637                } else {
638                    e
639                }
640            }));
641
642        let node = spanned(type_name().then_ignore(ws_char().repeated()))
643            .or_not()
644            .then(spanned(ident()))
645            .then(
646                node_space()
647                    .repeated()
648                    .at_least(1)
649                    .ignore_then(prop_or_arg())
650                    .repeated(),
651            )
652            .then(
653                node_space()
654                    .repeated()
655                    .ignore_then(
656                        begin_comment('-')
657                            .then_ignore(line_space().repeated())
658                            .or_not(),
659                    )
660                    .then(spanned(braced_nodes))
661                    .or_not(),
662            )
663            .then_ignore(node_space().repeated().then(node_terminator().or_not()))
664            .map(|(((type_name, node_name), line_items), opt_children)| {
665                let mut node = Node {
666                    type_name,
667                    node_name,
668                    properties: BTreeMap::new(),
669                    arguments: Vec::new(),
670                    children: match opt_children {
671                        Some((Some(_comment), _)) => None,
672                        Some((None, children)) => Some(children),
673                        None => None,
674                    },
675                };
676                for item in line_items {
677                    match item {
678                        Prop(name, value) => {
679                            node.properties.insert(name, value);
680                        }
681                        Arg(value) => {
682                            node.arguments.push(value);
683                        }
684                        Ignore => {}
685                    }
686                }
687                node
688            });
689
690        begin_comment('-')
691            .then_ignore(line_space().repeated())
692            .or_not()
693            .then(spanned(node))
694            .separated_by(line_space().repeated())
695            .allow_leading()
696            .allow_trailing()
697            .map(|vec| {
698                vec.into_iter()
699                    .filter_map(
700                        |(comment, node)| {
701                            if comment.is_none() {
702                                Some(node)
703                            } else {
704                                None
705                            }
706                        },
707                    )
708                    .collect()
709            })
710    })
711}
712
713pub(crate) fn document<S: Span>() -> impl Parser<char, Document<S>, Error = Error<S>> {
714    just('\u{FEFF}').or_not().ignore_then(nodes()).then_ignore(end()).map(|nodes| Document { nodes })
715}
716
717#[cfg(test)]
718mod test {
719    use super::{comment, ident, literal, ml_comment, string, type_name, ws};
720    use super::{nodes, number};
721    use crate::ast::{Decimal, Integer, Literal, Radix, TypeName};
722    use crate::errors::{Error, ParseError};
723    use crate::span::Span;
724    use crate::traits::sealed::Sealed;
725    use chumsky::prelude::*;
726    use miette::NamedSource;
727
728    macro_rules! err_eq {
729        ($left: expr, $right: expr) => {
730            let left = $left.unwrap_err();
731            let left: serde_json::Value = serde_json::from_str(&left).unwrap();
732            let right: serde_json::Value =
733                serde_json::from_str($right).unwrap();
734            assert_json_diff::assert_json_include!(
735                actual: left, expected: right);
736            //assert_json_diff::assert_json_eq!(left, right);
737        }
738    }
739
740    fn parse<P, T>(p: P, text: &str) -> Result<T, String>
741    where
742        P: Parser<char, T, Error = ParseError<Span>>,
743    {
744        p.then_ignore(end())
745            .parse(Span::stream(text))
746            .map_err(|errors| {
747                let source = text.to_string() + " ";
748                let e = Error {
749                    source_code: NamedSource::new("<test>", source),
750                    errors: errors.into_iter().map(Into::into).collect(),
751                };
752                let mut buf = String::with_capacity(512);
753                miette::GraphicalReportHandler::new()
754                    .render_report(&mut buf, &e)
755                    .unwrap();
756                println!("{}", buf);
757                buf.truncate(0);
758                miette::JSONReportHandler::new()
759                    .render_report(&mut buf, &e)
760                    .unwrap();
761                buf
762            })
763    }
764
765    #[test]
766    fn parse_ws() {
767        parse(ws(), "   ").unwrap();
768        parse(ws(), "text").unwrap_err();
769    }
770
771    #[test]
772    fn parse_comments() {
773        parse(comment(), "//hello").unwrap();
774        parse(comment(), "//hello\n").unwrap();
775        parse(ml_comment(), "/*nothing*/").unwrap();
776        parse(ml_comment(), "/*nothing**/").unwrap();
777        parse(ml_comment(), "/*no*thing*/").unwrap();
778        parse(ml_comment(), "/*no/**/thing*/").unwrap();
779        parse(ml_comment(), "/*no/*/**/*/thing*/").unwrap();
780        parse(ws().then(comment()), "   // hello").unwrap();
781        parse(
782            ws().then(comment()).then(ws()).then(comment()),
783            "   // hello\n   //world",
784        )
785        .unwrap();
786    }
787
788    #[test]
789    fn parse_comment_err() {
790        err_eq!(
791            parse(ws(), r#"/* comment"#),
792            r#"{
793            "message": "error parsing KDL",
794            "severity": "error",
795            "labels": [],
796            "related": [{
797                "message": "unclosed comment `/*`",
798                "severity": "error",
799                "filename": "<test>",
800                "labels": [
801                    {"label": "opened here",
802                    "span": {"offset": 0, "length": 2}},
803                    {"label": "expected `*/`",
804                    "span": {"offset": 10, "length": 0}}
805                ],
806                "related": []
807            }]
808        }"#
809        );
810        err_eq!(
811            parse(ws(), r#"/* com/*ment *"#),
812            r#"{
813            "message": "error parsing KDL",
814            "severity": "error",
815            "labels": [],
816            "related": [{
817                "message": "unclosed comment `/*`",
818                "severity": "error",
819                "filename": "<test>",
820                "labels": [
821                    {"label": "opened here",
822                    "span": {"offset": 0, "length": 2}},
823                    {"label": "expected `*/`",
824                    "span": {"offset": 14, "length": 0}}
825                ],
826                "related": []
827            }]
828        }"#
829        );
830        err_eq!(
831            parse(ws(), r#"/* com/*me*/nt *"#),
832            r#"{
833            "message": "error parsing KDL",
834            "severity": "error",
835            "labels": [],
836            "related": [{
837                "message": "unclosed comment `/*`",
838                "severity": "error",
839                "filename": "<test>",
840                "labels": [
841                    {"label": "opened here",
842                    "span": {"offset": 0, "length": 2}},
843                    {"label": "expected `*/`",
844                    "span": {"offset": 16, "length": 0}}
845                ],
846                "related": []
847            }]
848        }"#
849        );
850        err_eq!(
851            parse(ws(), r#"/* comment *"#),
852            r#"{
853            "message": "error parsing KDL",
854            "severity": "error",
855            "labels": [],
856            "related": [{
857                "message": "unclosed comment `/*`",
858                "severity": "error",
859                "filename": "<test>",
860                "labels": [
861                    {"label": "opened here",
862                    "span": {"offset": 0, "length": 2}},
863                    {"label": "expected `*/`",
864                    "span": {"offset": 12, "length": 0}}
865                ],
866                "related": []
867            }]
868        }"#
869        );
870        err_eq!(
871            parse(ws(), r#"/*/"#),
872            r#"{
873            "message": "error parsing KDL",
874            "severity": "error",
875            "labels": [],
876            "related": [{
877                "message": "unclosed comment `/*`",
878                "severity": "error",
879                "filename": "<test>",
880                "labels": [
881                    {"label": "opened here",
882                    "span": {"offset": 0, "length": 2}},
883                    {"label": "expected `*/`",
884                    "span": {"offset": 3, "length": 0}}
885                ],
886                "related": []
887            }]
888        }"#
889        );
890        // nothing is expected for comment or whitespace
891        err_eq!(
892            parse(ws(), r#"xxx"#),
893            r#"{
894            "message": "error parsing KDL",
895            "severity": "error",
896            "labels": [],
897            "related": [{
898                "message": "found `x`, expected whitespace",
899                "severity": "error",
900                "filename": "<test>",
901                "labels": [
902                    {"label": "unexpected token",
903                    "span": {"offset": 0, "length": 1}}
904                ],
905                "related": []
906            }]
907        }"#
908        );
909    }
910
911    #[test]
912    fn parse_str() {
913        //assert_eq!(&*parse(string(), r#"hello"#).unwrap(), "hello");
914        assert_eq!(&*parse(string(), r#""hello""#).unwrap(), "hello");
915        assert_eq!(&*parse(string(), r#""""#).unwrap(), "");
916        assert_eq!(&*parse(string(), r#""hel\"lo""#).unwrap(), "hel\"lo");
917        assert_eq!(
918            &*parse(string(), r#""hello\nworld!""#).unwrap(),
919            "hello\nworld!"
920        );
921        assert_eq!(&*parse(string(), r#""\u{1F680}""#).unwrap(), "🚀");
922    }
923
924    #[test]
925    fn parse_raw_str() {
926        assert_eq!(&*parse(string(), r#""hello""#).unwrap(), "hello");
927        assert_eq!(&*parse(string(), r##"#"world"#"##).unwrap(), "world");
928        assert_eq!(&*parse(string(), r##"#"world"#"##).unwrap(), "world");
929        assert_eq!(
930            &*parse(string(), r####"###"a\n"##b"###"####).unwrap(),
931            "a\\n\"##b"
932        );
933    }
934
935    #[test]
936    fn parse_str_err() {
937        err_eq!(
938            parse(string(), r#""hello"#),
939            r#"{
940            "message": "error parsing KDL",
941            "severity": "error",
942            "labels": [],
943            "related": [{
944                "message": "unclosed string `\"`",
945                "severity": "error",
946                "filename": "<test>",
947                "labels": [
948                    {"label": "opened here",
949                    "span": {"offset": 0, "length": 1}},
950                    {"label": "expected `\"`",
951                    "span": {"offset": 6, "length": 0}}
952                ],
953                "related": []
954            }]
955        }"#
956        );
957        err_eq!(
958            parse(string(), r#""he\u{FFFFFF}llo""#),
959            r#"{
960            "message": "error parsing KDL",
961            "severity": "error",
962            "labels": [],
963            "related": [{
964                "message": "converted integer out of range for `char`",
965                "severity": "error",
966                "filename": "<test>",
967                "labels": [
968                    {"label": "invalid character code",
969                    "span": {"offset": 5, "length": 8}}
970                ],
971                "related": []
972            }]
973        }"#
974        );
975        err_eq!(
976            parse(string(), r#""he\u{1234567}llo""#),
977            r#"{
978            "message": "error parsing KDL",
979            "severity": "error",
980            "labels": [],
981            "related": [{
982                "message": "found `7`, expected `}`",
983                "severity": "error",
984                "filename": "<test>",
985                "labels": [
986                    {"label": "unexpected token",
987                    "span": {"offset": 12, "length": 1}}
988                ],
989                "related": []
990            }]
991        }"#
992        );
993        err_eq!(
994            parse(string(), r#""he\u{1gh}llo""#),
995            r#"{
996            "message": "error parsing KDL",
997            "severity": "error",
998            "labels": [],
999            "related": [{
1000                "message": "found `g`, expected `}` or hexadecimal digit",
1001                "severity": "error",
1002                "filename": "<test>",
1003                "labels": [
1004                    {"label": "unexpected token",
1005                    "span": {"offset": 7, "length": 1}}
1006                ],
1007                "related": []
1008            }]
1009        }"#
1010        );
1011        err_eq!(
1012            parse(string(), r#""he\x01llo""#),
1013            r#"{
1014            "message": "error parsing KDL",
1015            "severity": "error",
1016            "labels": [],
1017            "related": [{
1018                "message":
1019                    "found `x`, expected `\"`, `\\`, `b`, `f`, `n`, `r`, `s`, `t` or `u`",
1020                "severity": "error",
1021                "filename": "<test>",
1022                "labels": [
1023                    {"label": "invalid escape char",
1024                    "span": {"offset": 4, "length": 1}}
1025                ],
1026                "related": []
1027            }]
1028        }"#
1029        );
1030        // Tests error recovery
1031        err_eq!(
1032            parse(string(), r#""he\u{FFFFFF}l\!lo""#),
1033            r#"{
1034            "message": "error parsing KDL",
1035            "severity": "error",
1036            "labels": [],
1037            "related": [{
1038                "message": "converted integer out of range for `char`",
1039                "severity": "error",
1040                "filename": "<test>",
1041                "labels": [
1042                    {"label": "invalid character code",
1043                    "span": {"offset": 5, "length": 8}}
1044                ],
1045                "related": []
1046            }, {
1047                "message":
1048                    "found `!`, expected `\"`, `\\`, `b`, `f`, `n`, `r`, `s`, `t` or `u`",
1049                "severity": "error",
1050                "filename": "<test>",
1051                "labels": [
1052                    {"label": "invalid escape char",
1053                    "span": {"offset": 15, "length": 1}}
1054                ],
1055                "related": []
1056            }]
1057        }"#
1058        );
1059    }
1060    #[test]
1061    fn parse_raw_str_err() {
1062        err_eq!(
1063            parse(string(), r#"#"hello"#),
1064            r##"{
1065            "message": "error parsing KDL",
1066            "severity": "error",
1067            "labels": [],
1068            "related": [{
1069                "message": "unclosed raw string `#\"`",
1070                "severity": "error",
1071                "filename": "<test>",
1072                "labels": [
1073                    {"label": "opened here",
1074                    "span": {"offset": 0, "length": 2}},
1075                    {"label": "expected `\"#`",
1076                    "span": {"offset": 7, "length": 0}}
1077                ],
1078                "related": []
1079            }]
1080        }"##
1081        );
1082        err_eq!(
1083            parse(string(), r###"#"hello""###),
1084            r###"{
1085            "message": "error parsing KDL",
1086            "severity": "error",
1087            "labels": [],
1088            "related": [{
1089                "message": "unclosed raw string `#\"`",
1090                "severity": "error",
1091                "filename": "<test>",
1092                "labels": [
1093                    {"label": "opened here",
1094                    "span": {"offset": 0, "length": 2}},
1095                    {"label": "expected `\"#`",
1096                    "span": {"offset": 8, "length": 0}}
1097                ],
1098                "related": []
1099            }]
1100        }"###
1101        );
1102        err_eq!(
1103            parse(string(), r####"###"hello"####),
1104            r####"{
1105            "message": "error parsing KDL",
1106            "severity": "error",
1107            "labels": [],
1108            "related": [{
1109                "message": "unclosed raw string `###\"`",
1110                "severity": "error",
1111                "filename": "<test>",
1112                "labels": [
1113                    {"label": "opened here",
1114                    "span": {"offset": 0, "length": 4}},
1115                    {"label": "expected `\"###`",
1116                    "span": {"offset": 9, "length": 0}}
1117                ],
1118                "related": []
1119            }]
1120        }"####
1121        );
1122        err_eq!(
1123            parse(string(), r####"###"hello"#world"####),
1124            r####"{
1125            "message": "error parsing KDL",
1126            "severity": "error",
1127            "labels": [],
1128            "related": [{
1129                "message": "unclosed raw string `###\"`",
1130                "severity": "error",
1131                "filename": "<test>",
1132                "labels": [
1133                    {"label": "opened here",
1134                    "span": {"offset": 0, "length": 4}},
1135                    {"label": "expected `\"###`",
1136                    "span": {"offset": 16, "length": 0}}
1137                ],
1138                "related": []
1139            }]
1140        }"####
1141        );
1142    }
1143
1144    #[test]
1145    fn parse_ident() {
1146        assert_eq!(&*parse(ident(), "abcdef").unwrap(), "abcdef");
1147        assert_eq!(&*parse(ident(), "xx_cd$yy").unwrap(), "xx_cd$yy");
1148        assert_eq!(&*parse(ident(), "-").unwrap(), "-");
1149        assert_eq!(&*parse(ident(), "--hello").unwrap(), "--hello");
1150        assert_eq!(&*parse(ident(), "--hello1234").unwrap(), "--hello1234");
1151        assert_eq!(&*parse(ident(), "--1").unwrap(), "--1");
1152        assert_eq!(&*parse(ident(), "++1").unwrap(), "++1");
1153        assert_eq!(&*parse(ident(), "-hello").unwrap(), "-hello");
1154        assert_eq!(&*parse(ident(), "+hello").unwrap(), "+hello");
1155        assert_eq!(&*parse(ident(), "-A").unwrap(), "-A");
1156        assert_eq!(&*parse(ident(), "+b").unwrap(), "+b");
1157        assert_eq!(
1158            &*parse(ident().then_ignore(ws()), "adef   ").unwrap(),
1159            "adef"
1160        );
1161        assert_eq!(
1162            &*parse(ident().then_ignore(ws()), "a123@   ").unwrap(),
1163            "a123@"
1164        );
1165        parse(ident(), "1abc").unwrap_err();
1166        parse(ident(), "-1").unwrap_err();
1167        parse(ident(), "-1test").unwrap_err();
1168        parse(ident(), "+1").unwrap_err();
1169    }
1170
1171    #[test]
1172    fn parse_literal() {
1173        assert_eq!(parse(literal(), "#true").unwrap(), Literal::Bool(true));
1174        assert_eq!(parse(literal(), "#false").unwrap(), Literal::Bool(false));
1175        assert_eq!(parse(literal(), "#null").unwrap(), Literal::Null);
1176        assert_eq!(parse(literal(), "#nan").unwrap(), Literal::Nan);
1177        assert_eq!(parse(literal(), "#inf").unwrap(), Literal::Inf);
1178        assert_eq!(parse(literal(), "#-inf").unwrap(), Literal::NegInf);
1179    }
1180
1181    #[test]
1182    fn exclude_keywords() {
1183        parse(nodes(), "item #true").unwrap();
1184
1185        // would be nice for this to error with "unexpected keyword #true", but
1186        // right now its reading it as an improperly formatted raw string.
1187        err_eq!(
1188            parse(nodes(), "#true \"item\""),
1189            r#"{
1190            "message": "error parsing KDL",
1191            "severity": "error",
1192            "labels": [],
1193            "related": [{
1194                "message":
1195                    "found `t`, expected `\"` or `#`",
1196                "severity": "error",
1197                "filename": "<test>",
1198                "labels": [
1199                    {"label": "unexpected token",
1200                    "span": {"offset": 1, "length": 1}}
1201                ],
1202                "related": []
1203            }]
1204        }"#
1205        );
1206
1207        err_eq!(
1208            parse(nodes(), "item #false=#true"),
1209            r#"{
1210            "message": "error parsing KDL",
1211            "severity": "error",
1212            "labels": [],
1213            "related": [{
1214                "message":
1215                    "found keyword, expected identifier or string",
1216                "severity": "error",
1217                "filename": "<test>",
1218                "labels": [
1219                    {"label": "unexpected keyword",
1220                    "span": {"offset": 5, "length": 6}}
1221                ],
1222                "related": []
1223            }]
1224        }"#
1225        );
1226
1227        err_eq!(
1228            parse(nodes(), "item 2=2"),
1229            r#"{
1230            "message": "error parsing KDL",
1231            "severity": "error",
1232            "labels": [],
1233            "related": [{
1234                "message": "numbers cannot be used as property names",
1235                "severity": "error",
1236                "filename": "<test>",
1237                "labels": [
1238                    {"label": "unexpected number",
1239                    "span": {"offset": 5, "length": 1}}
1240                ],
1241                "help": "consider enclosing in double quotes \"..\"",
1242                "related": []
1243            }]
1244        }"#
1245        );
1246    }
1247
1248    #[test]
1249    fn exclude_bare_keywords() {
1250        err_eq!(
1251            parse(nodes(), "item true"),
1252            r#"{
1253            "message": "error parsing KDL",
1254            "severity": "error",
1255            "labels": [],
1256            "related": [{
1257                "message":
1258                    "`true` is not allowed as a bare string",
1259                "severity": "error",
1260                "filename": "<test>",
1261                "labels": [
1262                    {"label": "illegal identifier",
1263                    "span": {"offset": 5, "length": 4}}
1264                ],
1265                "related": []
1266            }]
1267        }"#
1268        );
1269
1270        err_eq!(
1271            parse(nodes(), r#"true "item""#),
1272            r#"{
1273            "message": "error parsing KDL",
1274            "severity": "error",
1275            "labels": [],
1276            "related": [{
1277                "message":
1278                    "`true` is not allowed as a bare string",
1279                "severity": "error",
1280                "filename": "<test>",
1281                "labels": [
1282                    {"label": "illegal identifier",
1283                    "span": {"offset": 0, "length": 4}}
1284                ],
1285                "related": []
1286            }]
1287        }"#
1288        );
1289
1290        err_eq!(
1291            parse(nodes(), "item false=#true"),
1292            r#"{
1293            "message": "error parsing KDL",
1294            "severity": "error",
1295            "labels": [],
1296            "related": [{
1297                "message":
1298                    "`false` is not allowed as a bare string",
1299                "severity": "error",
1300                "filename": "<test>",
1301                "labels": [
1302                    {"label": "illegal identifier",
1303                    "span": {"offset": 5, "length": 5}}
1304                ],
1305                "related": []
1306            }]
1307        }"#
1308        );
1309    }
1310
1311    #[test]
1312    fn parse_type() {
1313        assert_eq!(
1314            parse(type_name(), "(abcdef)").unwrap(),
1315            TypeName::from_string("abcdef".into())
1316        );
1317        assert_eq!(
1318            parse(type_name(), "(xx_cd$yy)").unwrap(),
1319            TypeName::from_string("xx_cd$yy".into())
1320        );
1321        parse(type_name(), "(1abc)").unwrap_err();
1322        assert_eq!(
1323            parse(type_name(), "( abc)").unwrap(),
1324            TypeName::from_string("abc".into())
1325        );
1326        assert_eq!(
1327            parse(type_name(), "(abc )").unwrap(),
1328            TypeName::from_string("abc".into())
1329        );
1330    }
1331
1332    #[test]
1333    fn parse_type_err() {
1334        err_eq!(
1335            parse(type_name(), "(123)"),
1336            r#"{
1337            "message": "error parsing KDL",
1338            "severity": "error",
1339            "labels": [],
1340            "related": [{
1341                "message": "found number, expected identifier",
1342                "severity": "error",
1343                "filename": "<test>",
1344                "labels": [
1345                    {"label": "unexpected number",
1346                    "span": {"offset": 1, "length": 3}}
1347                ],
1348                "related": []
1349            }]
1350        }"#
1351        );
1352
1353        err_eq!(
1354            parse(type_name(), "(-1)"),
1355            r#"{
1356            "message": "error parsing KDL",
1357            "severity": "error",
1358            "labels": [],
1359            "related": [{
1360                "message": "found number, expected identifier",
1361                "severity": "error",
1362                "filename": "<test>",
1363                "labels": [
1364                    {"label": "unexpected number",
1365                    "span": {"offset": 1, "length": 2}}
1366                ],
1367                "related": []
1368            }]
1369        }"#
1370        );
1371    }
1372
1373    fn single<T, E: std::fmt::Debug>(r: Result<Vec<T>, E>) -> T {
1374        let mut v = r.unwrap();
1375        assert_eq!(v.len(), 1);
1376        v.remove(0)
1377    }
1378
1379    #[test]
1380    fn parse_node() {
1381        let nval = single(parse(nodes(), "hello"));
1382        assert_eq!(nval.node_name.as_ref(), "hello");
1383        assert_eq!(nval.type_name.as_ref(), None);
1384
1385        let nval = single(parse(nodes(), "\"123\""));
1386        assert_eq!(nval.node_name.as_ref(), "123");
1387        assert_eq!(nval.type_name.as_ref(), None);
1388
1389        let nval = single(parse(nodes(), "(typ)other"));
1390        assert_eq!(nval.node_name.as_ref(), "other");
1391        assert_eq!(nval.type_name.as_ref().map(|x| &***x), Some("typ"));
1392
1393        let nval = single(parse(nodes(), "(typ) \tafter-ws"));
1394        assert_eq!(nval.node_name.as_ref(), "after-ws");
1395        assert_eq!(nval.type_name.as_ref().map(|x| &***x), Some("typ"));
1396
1397        let nval = single(parse(nodes(), "(\"std::duration\")\"timeout\""));
1398        assert_eq!(nval.node_name.as_ref(), "timeout");
1399        assert_eq!(
1400            nval.type_name.as_ref().map(|x| &***x),
1401            Some("std::duration")
1402        );
1403
1404        let nval = single(parse(nodes(), "hello \"arg1\""));
1405        assert_eq!(nval.node_name.as_ref(), "hello");
1406        assert_eq!(nval.type_name.as_ref(), None);
1407        assert_eq!(nval.arguments.len(), 1);
1408        assert_eq!(nval.properties.len(), 0);
1409        assert_eq!(&*nval.arguments[0].literal, &Literal::String("arg1".into()));
1410
1411        let nval = single(parse(nodes(), "node \"true\""));
1412        assert_eq!(nval.node_name.as_ref(), "node");
1413        assert_eq!(nval.type_name.as_ref(), None);
1414        assert_eq!(nval.arguments.len(), 1);
1415        assert_eq!(nval.properties.len(), 0);
1416        assert_eq!(&*nval.arguments[0].literal, &Literal::String("true".into()));
1417
1418        let nval = single(parse(nodes(), "hello (string)\"arg1\""));
1419        assert_eq!(nval.node_name.as_ref(), "hello");
1420        assert_eq!(nval.type_name.as_ref(), None);
1421        assert_eq!(nval.arguments.len(), 1);
1422        assert_eq!(nval.properties.len(), 0);
1423        assert_eq!(&***nval.arguments[0].type_name.as_ref().unwrap(), "string");
1424        assert_eq!(&*nval.arguments[0].literal, &Literal::String("arg1".into()));
1425
1426        let nval = single(parse(nodes(), "hello (typ) \t\"after whitespace\""));
1427        assert_eq!(nval.node_name.as_ref(), "hello");
1428        assert_eq!(nval.type_name.as_ref(), None);
1429        assert_eq!(nval.arguments.len(), 1);
1430        assert_eq!(nval.properties.len(), 0);
1431        assert_eq!(&***nval.arguments[0].type_name.as_ref().unwrap(), "typ");
1432        assert_eq!(&*nval.arguments[0].literal, &Literal::String("after whitespace".into()));
1433
1434        let nval = single(parse(nodes(), "hello key=(string)\"arg1\""));
1435        assert_eq!(nval.node_name.as_ref(), "hello");
1436        assert_eq!(nval.type_name.as_ref(), None);
1437        assert_eq!(nval.arguments.len(), 0);
1438        assert_eq!(nval.properties.len(), 1);
1439        assert_eq!(
1440            &***nval
1441                .properties
1442                .get("key")
1443                .unwrap()
1444                .type_name
1445                .as_ref()
1446                .unwrap(),
1447            "string"
1448        );
1449        assert_eq!(
1450            &*nval.properties.get("key").unwrap().literal,
1451            &Literal::String("arg1".into())
1452        );
1453
1454        let nval = single(parse(nodes(), "hello key=\"arg1\""));
1455        assert_eq!(nval.node_name.as_ref(), "hello");
1456        assert_eq!(nval.type_name.as_ref(), None);
1457        assert_eq!(nval.arguments.len(), 0);
1458        assert_eq!(nval.properties.len(), 1);
1459        assert_eq!(
1460            &*nval.properties.get("key").unwrap().literal,
1461            &Literal::String("arg1".into())
1462        );
1463
1464        let nval = single(parse(nodes(), "parent {\nchild\n}"));
1465        assert_eq!(nval.node_name.as_ref(), "parent");
1466        assert_eq!(nval.children().len(), 1);
1467        assert_eq!(
1468            nval.children.as_ref().unwrap()[0].node_name.as_ref(),
1469            "child"
1470        );
1471
1472        let nval = single(parse(nodes(), "parent {\nchild1\nchild2\n}"));
1473        assert_eq!(nval.node_name.as_ref(), "parent");
1474        assert_eq!(nval.children().len(), 2);
1475        assert_eq!(
1476            nval.children.as_ref().unwrap()[0].node_name.as_ref(),
1477            "child1"
1478        );
1479        assert_eq!(
1480            nval.children.as_ref().unwrap()[1].node_name.as_ref(),
1481            "child2"
1482        );
1483
1484        let nval = single(parse(nodes(), "parent{\nchild3\n}"));
1485        assert_eq!(nval.node_name.as_ref(), "parent");
1486        assert_eq!(nval.children().len(), 1);
1487        assert_eq!(
1488            nval.children.as_ref().unwrap()[0].node_name.as_ref(),
1489            "child3"
1490        );
1491
1492        let nval = single(parse(nodes(), "parent \"x\"=1 {\nchild4\n}"));
1493        assert_eq!(nval.node_name.as_ref(), "parent");
1494        assert_eq!(nval.properties.len(), 1);
1495        assert_eq!(nval.children().len(), 1);
1496        assert_eq!(
1497            nval.children.as_ref().unwrap()[0].node_name.as_ref(),
1498            "child4"
1499        );
1500
1501        let nval = single(parse(nodes(), "parent \"x\" {\nchild4\n}"));
1502        assert_eq!(nval.node_name.as_ref(), "parent");
1503        assert_eq!(nval.arguments.len(), 1);
1504        assert_eq!(nval.children().len(), 1);
1505        assert_eq!(
1506            nval.children.as_ref().unwrap()[0].node_name.as_ref(),
1507            "child4"
1508        );
1509
1510        let nval = single(parse(nodes(), "parent \"x\"{\nchild5\n}"));
1511        assert_eq!(nval.node_name.as_ref(), "parent");
1512        assert_eq!(nval.arguments.len(), 1);
1513        assert_eq!(nval.children().len(), 1);
1514        assert_eq!(
1515            nval.children.as_ref().unwrap()[0].node_name.as_ref(),
1516            "child5"
1517        );
1518
1519        let nval = single(parse(nodes(), "hello /-\"skip_arg\" \"arg2\""));
1520        assert_eq!(nval.node_name.as_ref(), "hello");
1521        assert_eq!(nval.type_name.as_ref(), None);
1522        assert_eq!(nval.arguments.len(), 1);
1523        assert_eq!(nval.properties.len(), 0);
1524        assert_eq!(&*nval.arguments[0].literal, &Literal::String("arg2".into()));
1525
1526        let nval = single(parse(nodes(), "hello /- \"skip_arg\" \"arg2\""));
1527        assert_eq!(nval.node_name.as_ref(), "hello");
1528        assert_eq!(nval.type_name.as_ref(), None);
1529        assert_eq!(nval.arguments.len(), 1);
1530        assert_eq!(nval.properties.len(), 0);
1531        assert_eq!(&*nval.arguments[0].literal, &Literal::String("arg2".into()));
1532
1533        let nval = single(parse(nodes(), "hello prop1=\"1\" /-prop1=\"2\""));
1534        assert_eq!(nval.node_name.as_ref(), "hello");
1535        assert_eq!(nval.type_name.as_ref(), None);
1536        assert_eq!(nval.arguments.len(), 0);
1537        assert_eq!(nval.properties.len(), 1);
1538        assert_eq!(
1539            &*nval.properties.get("prop1").unwrap().literal,
1540            &Literal::String("1".into())
1541        );
1542
1543        let nval = single(parse(nodes(), "parent /-{\nchild\n}"));
1544        assert_eq!(nval.node_name.as_ref(), "parent");
1545        assert_eq!(nval.children().len(), 0);
1546    }
1547
1548    #[test]
1549    fn parse_node_whitespace() {
1550        let nval = single(parse(nodes(), "hello  {   }"));
1551        assert_eq!(nval.node_name.as_ref(), "hello");
1552        assert_eq!(nval.type_name.as_ref(), None);
1553
1554        let nval = single(parse(nodes(), "hello  {   }  "));
1555        assert_eq!(nval.node_name.as_ref(), "hello");
1556        assert_eq!(nval.type_name.as_ref(), None);
1557
1558        let nval = single(parse(nodes(), "hello "));
1559        assert_eq!(nval.node_name.as_ref(), "hello");
1560        assert_eq!(nval.type_name.as_ref(), None);
1561
1562        let nval = single(parse(nodes(), "hello   "));
1563        assert_eq!(nval.node_name.as_ref(), "hello");
1564        assert_eq!(nval.type_name.as_ref(), None);
1565    }
1566
1567    #[test]
1568    fn parse_node_err() {
1569        err_eq!(
1570            parse(nodes(), "hello{"),
1571            r#"{
1572            "message": "error parsing KDL",
1573            "severity": "error",
1574            "labels": [],
1575            "related": [{
1576                "message": "unclosed curly braces `{`",
1577                "severity": "error",
1578                "filename": "<test>",
1579                "labels": [
1580                    {"label": "opened here",
1581                    "span": {"offset": 5, "length": 1}},
1582                    {"label": "expected `}`",
1583                    "span": {"offset": 6, "length": 0}}
1584                ],
1585                "related": []
1586            }]
1587        }"#
1588        );
1589
1590        err_eq!(
1591            parse(nodes(), "1 + 2"),
1592            r#"{
1593            "message": "error parsing KDL",
1594            "severity": "error",
1595            "labels": [],
1596            "related": [{
1597                "message": "found number, expected identifier",
1598                "severity": "error",
1599                "filename": "<test>",
1600                "labels": [
1601                    {"label": "unexpected number",
1602                    "span": {"offset": 0, "length": 1}}
1603                ],
1604                "related": []
1605            }]
1606        }"#
1607        );
1608
1609        err_eq!(
1610            parse(nodes(), "-1 +2"),
1611            r#"{
1612            "message": "error parsing KDL",
1613            "severity": "error",
1614            "labels": [],
1615            "related": [{
1616                "message": "found number, expected identifier",
1617                "severity": "error",
1618                "filename": "<test>",
1619                "labels": [
1620                    {"label": "unexpected number",
1621                    "span": {"offset": 0, "length": 2}}
1622                ],
1623                "related": []
1624            }]
1625        }"#
1626        );
1627    }
1628
1629    #[test]
1630    fn parse_nodes() {
1631        let nval = parse(nodes(), "parent {\n/-  child\n}").unwrap();
1632        assert_eq!(nval.len(), 1);
1633        assert_eq!(nval[0].node_name.as_ref(), "parent");
1634        assert_eq!(nval[0].children().len(), 0);
1635
1636        let nval = parse(nodes(), "/-parent {\n  child\n}\nsecond").unwrap();
1637        assert_eq!(nval.len(), 1);
1638        assert_eq!(nval[0].node_name.as_ref(), "second");
1639        assert_eq!(nval[0].children().len(), 0);
1640    }
1641
1642    #[test]
1643    fn parse_number() {
1644        assert_eq!(
1645            parse(number(), "12").unwrap(),
1646            Literal::Int(Integer(Radix::Dec, "12".into()))
1647        );
1648        assert_eq!(
1649            parse(number(), "012").unwrap(),
1650            Literal::Int(Integer(Radix::Dec, "012".into()))
1651        );
1652        assert_eq!(
1653            parse(number(), "0").unwrap(),
1654            Literal::Int(Integer(Radix::Dec, "0".into()))
1655        );
1656        assert_eq!(
1657            parse(number(), "-012").unwrap(),
1658            Literal::Int(Integer(Radix::Dec, "-012".into()))
1659        );
1660        assert_eq!(
1661            parse(number(), "+0").unwrap(),
1662            Literal::Int(Integer(Radix::Dec, "+0".into()))
1663        );
1664        assert_eq!(
1665            parse(number(), "123_555").unwrap(),
1666            Literal::Int(Integer(Radix::Dec, "123555".into()))
1667        );
1668        assert_eq!(
1669            parse(number(), "123.555").unwrap(),
1670            Literal::Decimal(Decimal("123.555".into()))
1671        );
1672        assert_eq!(
1673            parse(number(), "+1_23.5_55E-17").unwrap(),
1674            Literal::Decimal(Decimal("+123.555E-17".into()))
1675        );
1676        assert_eq!(
1677            parse(number(), "123e+555").unwrap(),
1678            Literal::Decimal(Decimal("123e+555".into()))
1679        );
1680    }
1681
1682    #[test]
1683    fn parse_radix_number() {
1684        assert_eq!(
1685            parse(number(), "0x12").unwrap(),
1686            Literal::Int(Integer(Radix::Hex, "12".into()))
1687        );
1688        assert_eq!(
1689            parse(number(), "0xab_12").unwrap(),
1690            Literal::Int(Integer(Radix::Hex, "ab12".into()))
1691        );
1692        assert_eq!(
1693            parse(number(), "-0xab_12").unwrap(),
1694            Literal::Int(Integer(Radix::Hex, "-ab12".into()))
1695        );
1696        assert_eq!(
1697            parse(number(), "0o17").unwrap(),
1698            Literal::Int(Integer(Radix::Oct, "17".into()))
1699        );
1700        assert_eq!(
1701            parse(number(), "+0o17").unwrap(),
1702            Literal::Int(Integer(Radix::Oct, "+17".into()))
1703        );
1704        assert_eq!(
1705            parse(number(), "0b1010_101").unwrap(),
1706            Literal::Int(Integer(Radix::Bin, "1010101".into()))
1707        );
1708    }
1709
1710    #[test]
1711    fn parse_dashes() {
1712        let nval = parse(nodes(), "-").unwrap();
1713        assert_eq!(nval.len(), 1);
1714        assert_eq!(nval[0].node_name.as_ref(), "-");
1715        assert_eq!(nval[0].children().len(), 0);
1716
1717        let nval = parse(nodes(), "--").unwrap();
1718        assert_eq!(nval.len(), 1);
1719        assert_eq!(nval[0].node_name.as_ref(), "--");
1720        assert_eq!(nval[0].children().len(), 0);
1721
1722        let nval = parse(nodes(), "--1").unwrap();
1723        assert_eq!(nval.len(), 1);
1724        assert_eq!(nval[0].node_name.as_ref(), "--1");
1725        assert_eq!(nval[0].children().len(), 0);
1726
1727        let nval = parse(nodes(), "-\n-").unwrap();
1728        assert_eq!(nval.len(), 2);
1729        assert_eq!(nval[0].node_name.as_ref(), "-");
1730        assert_eq!(nval[0].children().len(), 0);
1731        assert_eq!(nval[1].node_name.as_ref(), "-");
1732        assert_eq!(nval[1].children().len(), 0);
1733
1734        let nval = parse(nodes(), "node -1 --x=2").unwrap();
1735        assert_eq!(nval.len(), 1);
1736        assert_eq!(nval[0].arguments.len(), 1);
1737        assert_eq!(nval[0].properties.len(), 1);
1738        assert_eq!(
1739            &*nval[0].arguments[0].literal,
1740            &Literal::Int(Integer(Radix::Dec, "-1".into()))
1741        );
1742        assert_eq!(
1743            &*nval[0].properties.get("--x").unwrap().literal,
1744            &Literal::Int(Integer(Radix::Dec, "2".into()))
1745        );
1746    }
1747
1748    #[test]
1749    fn parse_property_ws() {
1750        let variants = [
1751            "node a =b",
1752            "node a= b",
1753            "node a     =       b",
1754            "node\ta\t=\tb",
1755        ];
1756        for ea_variant in variants {
1757            let nval = parse(nodes(), ea_variant).unwrap();
1758            assert_eq!(nval.len(), 1);
1759            assert_eq!(nval[0].node_name.as_ref(), "node");
1760            assert_eq!(nval[0].arguments.len(), 0);
1761            assert_eq!(nval[0].properties.len(), 1);
1762            assert_eq!(
1763                &*nval[0].properties.get("a").unwrap().literal,
1764                &Literal::String("b".into())
1765            );
1766        }
1767
1768        err_eq!(
1769            parse(nodes(), "node a\\\n=b"),
1770            r#"{
1771            "message": "error parsing KDL",
1772            "severity": "error",
1773            "labels": [],
1774            "related": [{
1775                "message": "found `=`, expected `\"`, `#`, `(`, `+`, `-`, `0`, `;`, `\\`, `{`, `#-inf`, `#false`, `#inf`, `#nan`, `#null`, `#true`, letter, newline, whitespace or end of input",
1776                "severity": "error",
1777                "filename": "<test>",
1778                "labels": [
1779                    {"label": "unexpected token",
1780                    "span": {"offset": 8, "length": 1}}
1781                ],
1782                "related": []
1783            }]
1784        }"#
1785        );
1786    }
1787}