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')) .or(just('\x0C')) .or(just('\u{0085}')) .or(just('\u{2028}')) .or(just('\u{2029}')) .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 '\u{00a0}' | '\u{1680}' |
79 '\u{2000}'..='\u{200A}' |
80 '\u{202F}' | '\u{205F}' | '\u{3000}' |
81 '\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 '\u{00a0}' | '\u{1680}' |
94 '\u{2000}'..='\u{200A}' |
95 '\u{202F}' | '\u{205F}' | '\u{3000}' |
96 '\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 '\u{00a0}' | '\u{1680}' |
109 '\u{2000}'..='\u{200A}' |
110 '\u{202F}' | '\u{205F}' | '\u{3000}' |
111 '\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 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 number().map(Err),
298 bare_ident().map(Ok),
299 string().map(Ok),
300 ))
301 .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 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 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 }
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 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 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