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')) .or(just('\x0C')) .or(just('\x0B')) .or(just('\u{0085}')) .or(just('\u{2028}')) .or(just('\u{2029}')) .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 '\u{00a0}' | '\u{1680}' |
77 '\u{2000}'..='\u{200A}' |
78 '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
79 '\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 '\u{00a0}' | '\u{1680}' |
94 '\u{2000}'..='\u{200A}' |
95 '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
96 '\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 '\u{00a0}' | '\u{1680}' |
111 '\u{2000}'..='\u{200A}' |
112 '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
113 '\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 '\u{00a0}' | '\u{1680}' |
128 '\u{2000}'..='\u{200A}' |
129 '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' |
130 '\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 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 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 id_sans_sign_dig_point().chain(id_char().repeated()),
310 sign.chain(id_sans_dig_point().chain(id_char().repeated()).or_not()),
312 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 number().map(Err),
366 bare_ident().map(Ok),
367 string().map(Ok),
368 ))
369 .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 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 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 }
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 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");
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 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 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}