knuffel/
grammar.rs

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