1use crate::errors::UserError;
6use nom::branch::alt;
7use nom::bytes::complete::{escaped, is_not, tag};
8use nom::character::complete::{
9 char, digit1, hex_digit1, line_ending, multispace1, not_line_ending, one_of,
10};
11use nom::combinator::{map, map_res, opt, value};
12use nom::error::{ErrorKind, ParseError};
13use nom::multi::{many0, separated_list1};
14use nom::sequence::{delimited, preceded};
15use nom::{IResult, Input, Parser};
16use nom_locate::LocatedSpan;
17use regex::Regex;
18use std::fmt;
19use std::sync::LazyLock;
20use thiserror::Error;
21
22pub type NomSpan<'a> = LocatedSpan<&'a str>;
23
24#[derive(Debug, Clone, PartialEq)]
25pub struct Span<'a> {
26 pub offset: usize,
27 pub line: u32,
28 pub fragment: &'a str,
29}
30
31impl<'a> Span<'a> {
32 pub fn new() -> Self {
33 Span { offset: 0, line: 1, fragment: "" }
34 }
35
36 pub fn from_to(from: &NomSpan<'a>, to: &NomSpan) -> Self {
37 Span {
38 offset: from.location_offset(),
39 line: from.location_line(),
40 fragment: &from.fragment()[..to.location_offset() - from.location_offset()],
41 }
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46pub struct CompoundIdentifier {
47 pub namespace: Vec<String>,
48 pub name: String,
49}
50
51impl fmt::Display for CompoundIdentifier {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 if self.namespace.is_empty() {
54 write!(f, "{}", self.name)
55 } else {
56 write!(f, "{}.{}", self.namespace.join("."), self.name)
57 }
58 }
59}
60
61impl CompoundIdentifier {
62 pub fn nest(&self, name: String) -> CompoundIdentifier {
63 let mut namespace = self.namespace.clone();
64 namespace.push(self.name.clone());
65 CompoundIdentifier { namespace, name }
66 }
67
68 pub fn parent(&self) -> Option<CompoundIdentifier> {
69 let mut namespace = self.namespace.clone();
70 let name = namespace.pop()?;
71 Some(CompoundIdentifier { namespace, name })
72 }
73}
74
75#[derive(Debug, Clone, PartialEq)]
76pub struct Include {
77 pub name: CompoundIdentifier,
78 pub alias: Option<String>,
79}
80
81#[derive(Debug, Clone, PartialEq)]
82pub enum Value {
83 NumericLiteral(u64),
84 StringLiteral(String),
85 BoolLiteral(bool),
86 Identifier(CompoundIdentifier),
87}
88
89#[derive(Debug, Clone, PartialEq)]
90pub enum NodeType {
91 Primary,
92 Additional,
93 Optional,
94}
95
96#[derive(Debug, Error, Clone, PartialEq)]
97pub enum BindParserError {
98 Type(String),
99 StringLiteral(String),
100 NumericLiteral(String),
101 BoolLiteral(String),
102 Identifier(String),
103 Semicolon(String),
104 Assignment(String),
105 ListStart(String),
106 ListEnd(String),
107 ListSeparator(String),
108 LibraryKeyword(String),
109 UsingKeyword(String),
110 AsKeyword(String),
111 IfBlockStart(String),
112 IfBlockEnd(String),
113 IfKeyword(String),
114 ElseKeyword(String),
115 ConditionOp(String),
116 ConditionValue(String),
117 AcceptKeyword(String),
118 TrueKeyword(String),
119 FalseKeyword(String),
120 NoStatements(String),
121 NoNodes(String),
122 Eof(String),
123 CompositeKeyword(String),
124 NodeKeyword(String),
125 PrimaryOrOptionalKeyword(String),
126 OnePrimaryNode(String),
127 InvalidNodeName(String),
128 DuplicateNodeName(String),
129 UnterminatedComment,
130 Unknown(String, ErrorKind),
131}
132
133impl fmt::Display for BindParserError {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "{}", UserError::from(self.clone()))
136 }
137}
138
139impl ParseError<NomSpan<'_>> for BindParserError {
140 fn from_error_kind(input: NomSpan, kind: ErrorKind) -> Self {
141 BindParserError::Unknown(input.fragment().to_string(), kind)
142 }
143
144 fn append(_input: NomSpan, _kind: ErrorKind, e: Self) -> Self {
145 e
146 }
147}
148
149pub fn string_literal(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
150 let escapable = escaped(is_not(r#"\""#), '\\', one_of(r#"\""#));
151 let literal = delimited(char('"'), escapable, char('"'));
152 map_err(map(literal, |s: NomSpan| s.fragment().to_string()), BindParserError::StringLiteral)
153 .parse(input)
154}
155
156pub fn numeric_literal(input: NomSpan) -> IResult<NomSpan, u64, BindParserError> {
157 let base10 = map_res(digit1, |s: NomSpan| u64::from_str_radix(s.fragment(), 10));
158 let base16 = map_res(preceded(tag("0x"), hex_digit1), |s: NomSpan| {
159 u64::from_str_radix(s.fragment(), 16)
160 });
161 map_err(alt((base16, base10)), BindParserError::NumericLiteral).parse(input)
163}
164
165pub fn bool_literal(input: NomSpan) -> IResult<NomSpan, bool, BindParserError> {
166 let true_ = value(true, tag("true"));
167 let false_ = value(false, tag("false"));
168 map_err(alt((true_, false_)), BindParserError::BoolLiteral).parse(input)
169}
170
171pub fn identifier(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
172 static RE: LazyLock<Regex> =
173 LazyLock::new(|| Regex::new(r"^[a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?").unwrap());
174 if let Some(mat) = RE.find(input.fragment()) {
175 Ok((
176 input.take_from(mat.end()),
177 input.take(mat.end()).take_from(mat.start()).fragment().to_string(),
178 ))
179 } else {
180 Err(nom::Err::Error(BindParserError::Identifier(input.fragment().to_string())))
181 }
182}
183
184pub fn compound_identifier(
185 input: NomSpan,
186) -> IResult<NomSpan, CompoundIdentifier, BindParserError> {
187 let (input, mut segments) = separated_list1(tag("."), identifier).parse(input)?;
188 let name = segments.pop().unwrap();
190 Ok((input, CompoundIdentifier { namespace: segments, name }))
191}
192
193pub fn condition_value(input: NomSpan) -> IResult<NomSpan, Value, BindParserError> {
194 let string = map(ws(string_literal), Value::StringLiteral);
195 let number = map(ws(numeric_literal), Value::NumericLiteral);
196 let boolean = map(ws(bool_literal), Value::BoolLiteral);
197 let identifer = map(ws(compound_identifier), Value::Identifier);
198
199 alt((string, number, boolean, identifer))
200 .parse(input)
201 .or_else(|_| Err(nom::Err::Error(BindParserError::ConditionValue(input.to_string()))))
202}
203
204pub fn using(input: NomSpan) -> IResult<NomSpan, Include, BindParserError> {
205 let as_keyword = ws(map_err(tag("as"), BindParserError::AsKeyword));
206 let (input, name) = ws(compound_identifier).parse(input)?;
207 let (input, alias) = opt(preceded(as_keyword, ws(identifier))).parse(input)?;
208 Ok((input, Include { name, alias }))
209}
210
211pub fn using_list(input: NomSpan) -> IResult<NomSpan, Vec<Include>, BindParserError> {
212 let using_keyword = ws(map_err(tag("using"), BindParserError::UsingKeyword));
213 let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
214 let using = delimited(using_keyword, ws(using), terminator);
215 many0(ws(using)).parse(input)
216}
217
218pub fn many_until_eof<'a, O, F>(
222 mut f: F,
223) -> impl Parser<NomSpan<'a>, Output = Vec<O>, Error = BindParserError>
224where
225 F: Parser<NomSpan<'a>, Output = O, Error = BindParserError>,
226{
227 move |mut input: NomSpan<'a>| {
228 let mut result = vec![];
229 loop {
230 if skip_ws(input)?.fragment().len() == 0 {
232 return Ok((skip_ws(input)?, result));
233 }
234
235 let (next_input, res) = f.parse(input)?;
236 if input.fragment().len() == next_input.fragment().len() {
237 panic!(
238 "many_until_eof called on an optional parser. This will result in an infinite loop"
239 );
240 }
241 input = next_input;
242 result.push(res);
243 }
244 }
245}
246
247pub fn ws<'a, O, F>(f: F) -> impl Parser<NomSpan<'a>, Output = O, Error = BindParserError>
251where
252 F: Parser<NomSpan<'a>, Output = O, Error = BindParserError>,
253{
254 preceded(comment_or_whitespace, f)
255}
256
257pub fn skip_ws(input: NomSpan) -> Result<NomSpan, nom::Err<BindParserError>> {
258 let (input, _) = comment_or_whitespace(input)?;
259 Ok(input)
260}
261
262fn comment_or_whitespace(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
263 let multispace = value((), multispace1);
264 value((), many0(alt((multispace, multiline_comment, singleline_comment)))).parse(input)
265}
266
267fn multiline_comment(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
269 let (input, _) = tag("/*").parse(input)?;
270 let mut iter = input.fragment().char_indices().peekable();
271 let mut stack = 1;
272 while let (Some((_, first)), Some((_, second))) = (iter.next(), iter.peek()) {
273 if first == '/' && *second == '*' {
274 stack += 1;
275 iter.next();
276 } else if first == '*' && *second == '/' {
277 stack -= 1;
278 iter.next();
279 if stack == 0 {
280 break;
281 }
282 }
283 }
284 if stack != 0 {
285 return Err(nom::Err::Failure(BindParserError::UnterminatedComment));
286 }
287
288 let consumed = if let Some((index, _)) = iter.peek() { *index } else { input.fragment().len() };
289 Ok((input.take_from(consumed), ()))
290}
291
292fn singleline_comment(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
294 value((), (tag("//"), not_line_ending, line_ending)).parse(input)
295}
296
297pub fn map_err<'a, O, P, G>(
299 mut parser: P,
300 f: G,
301) -> impl Parser<NomSpan<'a>, Output = O, Error = BindParserError>
302where
303 P: Parser<NomSpan<'a>, Output = O, Error = (NomSpan<'a>, ErrorKind)>,
304 G: Fn(String) -> BindParserError,
305{
306 move |input: NomSpan<'a>| {
307 parser.parse(input).map_err(|e| match e {
308 nom::Err::Error((i, _)) => nom::Err::Error(f(i.to_string())),
309 nom::Err::Failure((i, _)) => nom::Err::Failure(f(i.to_string())),
310 nom::Err::Incomplete(_) => {
311 unreachable!("Parser should never generate Incomplete errors")
312 }
313 })
314 }
315}
316
317#[macro_export]
318macro_rules! make_identifier{
319 ( $name:tt ) => {
320 {
321 CompoundIdentifier {
322 namespace: Vec::new(),
323 name: String::from($name),
324 }
325 }
326 };
327 ( $namespace:tt , $($rest:tt)* ) => {
328 {
329 let mut identifier = make_identifier!($($rest)*);
330 identifier.namespace.insert(0, String::from($namespace));
331 identifier
332 }
333 };
334}
335
336#[cfg(test)]
337pub mod test {
338 use super::*;
339
340 pub fn check_result<O: PartialEq + fmt::Debug>(
341 result: IResult<NomSpan, O, BindParserError>,
342 expected_input_fragment: &str,
343 expected_output: O,
344 ) {
345 match result {
346 Ok((input, output)) => {
347 assert_eq!(input.fragment(), &expected_input_fragment);
348 assert_eq!(output, expected_output);
349 }
350 Err(e) => {
351 panic!("{:#?}", e);
352 }
353 }
354 }
355
356 mod string_literal {
357 use super::*;
358
359 #[test]
360 fn basic() {
361 check_result(
363 string_literal(NomSpan::new(r#""abc 123"xyz"#)),
364 "xyz",
365 "abc 123".to_string(),
366 );
367 }
368
369 #[test]
370 fn match_once() {
371 check_result(
372 string_literal(NomSpan::new(r#""abc""123""#)),
373 r#""123""#,
374 "abc".to_string(),
375 );
376 }
377
378 #[test]
379 fn escaped() {
380 check_result(
381 string_literal(NomSpan::new(r#""abc \"esc\" xyz""#)),
382 "",
383 r#"abc \"esc\" xyz"#.to_string(),
384 );
385 }
386
387 #[test]
388 fn requires_quotations() {
389 assert_eq!(
390 string_literal(NomSpan::new(r#"abc"#)),
391 Err(nom::Err::Error(BindParserError::StringLiteral("abc".to_string())))
392 );
393 }
394
395 #[test]
396 fn empty() {
397 assert_eq!(
398 string_literal(NomSpan::new("")),
399 Err(nom::Err::Error(BindParserError::StringLiteral("".to_string())))
400 );
401 }
402 }
403
404 mod numeric_literals {
405 use super::*;
406
407 #[test]
408 fn decimal() {
409 check_result(numeric_literal(NomSpan::new("123")), "", 123);
410 }
411
412 #[test]
413 fn hex() {
414 check_result(numeric_literal(NomSpan::new("0x123")), "", 0x123);
415 check_result(numeric_literal(NomSpan::new("0xabcdef")), "", 0xabcdef);
416 check_result(numeric_literal(NomSpan::new("0xABCDEF")), "", 0xabcdef);
417 check_result(numeric_literal(NomSpan::new("0x123abc")), "", 0x123abc);
418
419 assert_eq!(
421 numeric_literal(NomSpan::new("abc123")),
422 Err(nom::Err::Error(BindParserError::NumericLiteral("abc123".to_string())))
423 );
424 check_result(numeric_literal(NomSpan::new("123abc")), "abc", 123);
425 }
426
427 #[test]
428 fn non_numbers() {
429 assert_eq!(
430 numeric_literal(NomSpan::new("xyz")),
431 Err(nom::Err::Error(BindParserError::NumericLiteral("xyz".to_string())))
432 );
433
434 assert_eq!(
436 numeric_literal(NomSpan::new("-1")),
437 Err(nom::Err::Error(BindParserError::NumericLiteral("-1".to_string())))
438 );
439 }
440
441 #[test]
442 fn overflow() {
443 check_result(
445 numeric_literal(NomSpan::new("18446744073709551615")),
446 "",
447 18446744073709551615,
448 );
449 check_result(
450 numeric_literal(NomSpan::new("0xffffffffffffffff")),
451 "",
452 18446744073709551615,
453 );
454 assert_eq!(
455 numeric_literal(NomSpan::new("18446744073709551616")),
456 Err(nom::Err::Error(BindParserError::NumericLiteral(
457 "18446744073709551616".to_string()
458 )))
459 );
460 check_result(
462 numeric_literal(NomSpan::new("0x10000000000000000")),
463 "x10000000000000000",
464 0,
465 );
466 }
467
468 #[test]
469 fn empty() {
470 assert_eq!(
472 numeric_literal(NomSpan::new("")),
473 Err(nom::Err::Error(BindParserError::NumericLiteral("".to_string())))
474 );
475 }
476 }
477
478 mod bool_literals {
479 use super::*;
480
481 #[test]
482 fn basic() {
483 check_result(bool_literal(NomSpan::new("true")), "", true);
484 check_result(bool_literal(NomSpan::new("false")), "", false);
485 }
486
487 #[test]
488 fn non_bools() {
489 assert_eq!(
491 bool_literal(NomSpan::new("tralse")),
492 Err(nom::Err::Error(BindParserError::BoolLiteral("tralse".to_string())))
493 );
494 }
495
496 #[test]
497 fn empty() {
498 assert_eq!(
500 bool_literal(NomSpan::new("")),
501 Err(nom::Err::Error(BindParserError::BoolLiteral("".to_string())))
502 );
503 }
504 }
505
506 mod identifiers {
507 use super::*;
508
509 #[test]
510 fn basic() {
511 check_result(identifier(NomSpan::new("abc_123_ABC")), "", "abc_123_ABC".to_string());
513
514 check_result(identifier(NomSpan::new("abc_123_ABC ")), " ", "abc_123_ABC".to_string());
516 check_result(identifier(NomSpan::new("abc_123_ABC;")), ";", "abc_123_ABC".to_string());
517 }
518
519 #[test]
520 fn invalid() {
521 assert_eq!(
523 identifier(NomSpan::new("_abc")),
524 Err(nom::Err::Error(BindParserError::Identifier("_abc".to_string())))
525 );
526
527 check_result(identifier(NomSpan::new("abc_")), "_", "abc".to_string());
529 }
530
531 #[test]
532 fn empty() {
533 assert_eq!(
535 identifier(NomSpan::new("")),
536 Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
537 );
538 }
539 }
540
541 mod compound_identifiers {
542 use super::*;
543
544 #[test]
545 fn single_identifier() {
546 check_result(
548 compound_identifier(NomSpan::new("abc_123_ABC")),
549 "",
550 make_identifier!["abc_123_ABC"],
551 );
552 }
553
554 #[test]
555 fn multiple_identifiers() {
556 check_result(
558 compound_identifier(NomSpan::new("abc.def")),
559 "",
560 make_identifier!["abc", "def"],
561 );
562 check_result(
563 compound_identifier(NomSpan::new("abc.def.ghi")),
564 "",
565 make_identifier!["abc", "def", "ghi"],
566 );
567 }
568
569 #[test]
570 fn empty() {
571 assert_eq!(
573 compound_identifier(NomSpan::new(".")),
574 Err(nom::Err::Error(BindParserError::Identifier(".".to_string())))
575 );
576 assert_eq!(
577 compound_identifier(NomSpan::new(".abc")),
578 Err(nom::Err::Error(BindParserError::Identifier(".abc".to_string())))
579 );
580 check_result(
581 compound_identifier(NomSpan::new("abc..def")),
582 "..def",
583 make_identifier!["abc"],
584 );
585 check_result(compound_identifier(NomSpan::new("abc.")), ".", make_identifier!["abc"]);
586
587 assert_eq!(
589 compound_identifier(NomSpan::new("")),
590 Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
591 );
592 }
593 }
594
595 mod condition_values {
596 use super::*;
597
598 #[test]
599 fn string() {
600 check_result(
601 condition_value(NomSpan::new(r#""abc""#)),
602 "",
603 Value::StringLiteral("abc".to_string()),
604 );
605 }
606
607 #[test]
608 fn bool() {
609 check_result(condition_value(NomSpan::new("true")), "", Value::BoolLiteral(true));
610 }
611
612 #[test]
613 fn number() {
614 check_result(condition_value(NomSpan::new("123")), "", Value::NumericLiteral(123));
615 }
616
617 #[test]
618 fn identifier() {
619 check_result(
620 condition_value(NomSpan::new("abc")),
621 "",
622 Value::Identifier(make_identifier!["abc"]),
623 );
624 }
625
626 #[test]
627 fn empty() {
628 assert_eq!(
630 condition_value(NomSpan::new("")),
631 Err(nom::Err::Error(BindParserError::ConditionValue("".to_string())))
632 );
633 }
634 }
635
636 mod using_lists {
637 use super::*;
638
639 #[test]
640 fn one_include() {
641 check_result(
642 using_list(NomSpan::new("using test;")),
643 "",
644 vec![Include { name: make_identifier!["test"], alias: None }],
645 );
646 }
647
648 #[test]
649 fn multiple_includes() {
650 check_result(
651 using_list(NomSpan::new("using abc;using def;")),
652 "",
653 vec![
654 Include { name: make_identifier!["abc"], alias: None },
655 Include { name: make_identifier!["def"], alias: None },
656 ],
657 );
658 check_result(
659 using_list(NomSpan::new("using abc;using def;using ghi;")),
660 "",
661 vec![
662 Include { name: make_identifier!["abc"], alias: None },
663 Include { name: make_identifier!["def"], alias: None },
664 Include { name: make_identifier!["ghi"], alias: None },
665 ],
666 );
667 }
668
669 #[test]
670 fn compound_identifiers() {
671 check_result(
672 using_list(NomSpan::new("using abc.def;")),
673 "",
674 vec![Include { name: make_identifier!["abc", "def"], alias: None }],
675 );
676 }
677
678 #[test]
679 fn aliases() {
680 check_result(
681 using_list(NomSpan::new("using abc.def as one;using ghi as two;")),
682 "",
683 vec![
684 Include {
685 name: make_identifier!["abc", "def"],
686 alias: Some("one".to_string()),
687 },
688 Include { name: make_identifier!["ghi"], alias: Some("two".to_string()) },
689 ],
690 );
691 }
692
693 #[test]
694 fn whitespace() {
695 check_result(
696 using_list(NomSpan::new(" using abc\t as one ;\n using def ; ")),
697 " ",
698 vec![
699 Include { name: make_identifier!["abc"], alias: Some("one".to_string()) },
700 Include { name: make_identifier!["def"], alias: None },
701 ],
702 );
703 check_result(
704 using_list(NomSpan::new("usingabc;")),
705 "",
706 vec![Include { name: make_identifier!["abc"], alias: None }],
707 );
708 }
709
710 #[test]
711 fn invalid() {
712 check_result(using_list(NomSpan::new("using abc")), "using abc", vec![]);
714 }
715
716 #[test]
717 fn empty() {
718 check_result(using_list(NomSpan::new("")), "", vec![]);
719 }
720 }
721
722 mod whitespace {
723 use super::*;
724
725 #[test]
726 fn multiline_comments() {
727 check_result(multiline_comment(NomSpan::new("/*one*/")), "", ());
728 check_result(multiline_comment(NomSpan::new("/*one*/two")), "two", ());
729 check_result(
730 multiline_comment(NomSpan::new("/*one/*two*/three/*four*/five*/six")),
731 "six",
732 (),
733 );
734 check_result(multiline_comment(NomSpan::new("/*/*one*/*/two")), "two", ());
735 assert_eq!(
736 multiline_comment(NomSpan::new("/*one")),
737 Err(nom::Err::Failure(BindParserError::UnterminatedComment))
738 );
739
740 let (input, _) = multiline_comment(NomSpan::new("/*\n/*one\n*/*/two")).unwrap();
742 assert_eq!(input.location_offset(), 13);
743 assert_eq!(input.location_line(), 3);
744 assert_eq!(input.fragment(), &"two");
745
746 let (input, _) = multiline_comment(NomSpan::new("/*\n/*one\n*/*/")).unwrap();
747 assert_eq!(input.location_offset(), 13);
748 assert_eq!(input.location_line(), 3);
749 assert_eq!(input.fragment(), &"");
750 }
751
752 #[test]
753 fn singleline_comments() {
754 check_result(singleline_comment(NomSpan::new("//one\ntwo")), "two", ());
755 check_result(singleline_comment(NomSpan::new("//one\r\ntwo")), "two", ());
756 }
757
758 #[test]
759 fn whitespace() {
760 let test = || map(tag("test"), |s: NomSpan| s.fragment().to_string());
761 check_result(ws(test()).parse(NomSpan::new("test")), "", "test".to_string());
762
763 check_result(ws(test()).parse(NomSpan::new(" \n\t\r\ntest")), "", "test".to_string());
764 check_result(
765 ws(test()).parse(NomSpan::new("test \n\t\r\n")),
766 " \n\t\r\n",
767 "test".to_string(),
768 );
769
770 check_result(
771 ws(test()).parse(NomSpan::new(" // test \n test // test \n ")),
772 " // test \n ",
773 "test".to_string(),
774 );
775
776 check_result(
777 ws(test()).parse(NomSpan::new(" /* test */ test /* test */ ")),
778 " /* test */ ",
779 "test".to_string(),
780 );
781 }
782
783 #[test]
784 fn skip_whitespace() {
785 let result = skip_ws(NomSpan::new("test")).unwrap();
786 assert_eq!(result.location_offset(), 0);
787 assert_eq!(result.location_line(), 1);
788 assert_eq!(result.fragment(), &"test");
789
790 let result = skip_ws(NomSpan::new(" \n\t\r\ntest \n\t\r\n")).unwrap();
791 assert_eq!(result.location_offset(), 5);
792 assert_eq!(result.location_line(), 3);
793 assert_eq!(result.fragment(), &"test \n\t\r\n");
794
795 let result = skip_ws(NomSpan::new(" // test \n test // test \n ")).unwrap();
796 assert_eq!(result.location_offset(), 11);
797 assert_eq!(result.location_line(), 2);
798 assert_eq!(result.fragment(), &"test // test \n ");
799
800 let result = skip_ws(NomSpan::new(" /* test */ test /* test */ ")).unwrap();
801 assert_eq!(result.location_offset(), 12);
802 assert_eq!(result.location_line(), 1);
803 assert_eq!(result.fragment(), &"test /* test */ ");
804 }
805 }
806}