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_lite::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 ParentType {
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 NoParents(String),
122 Eof(String),
123 CompositeKeyword(String),
124 ParentKeyword(String),
125 DeprecatedNodeKeyword(String),
126 PrimaryOrOptionalKeyword(String),
127 OnePrimaryParent(String),
128 InvalidParentName(String),
129 DuplicateParentName(String),
130 UnterminatedComment,
131 Unknown(String, ErrorKind),
132}
133
134impl fmt::Display for BindParserError {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 write!(f, "{}", UserError::from(self.clone()))
137 }
138}
139
140impl ParseError<NomSpan<'_>> for BindParserError {
141 fn from_error_kind(input: NomSpan, kind: ErrorKind) -> Self {
142 BindParserError::Unknown(input.fragment().to_string(), kind)
143 }
144
145 fn append(_input: NomSpan, _kind: ErrorKind, e: Self) -> Self {
146 e
147 }
148}
149
150pub fn string_literal(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
151 let escapable = escaped(is_not(r#"\""#), '\\', one_of(r#"\""#));
152 let literal = delimited(char('"'), escapable, char('"'));
153 map_err(map(literal, |s: NomSpan| s.fragment().to_string()), BindParserError::StringLiteral)
154 .parse(input)
155}
156
157pub fn numeric_literal(input: NomSpan) -> IResult<NomSpan, u64, BindParserError> {
158 let base10 = map_res(digit1, |s: NomSpan| u64::from_str_radix(s.fragment(), 10));
159 let base16 = map_res(preceded(tag("0x"), hex_digit1), |s: NomSpan| {
160 u64::from_str_radix(s.fragment(), 16)
161 });
162 map_err(alt((base16, base10)), BindParserError::NumericLiteral).parse(input)
164}
165
166pub fn bool_literal(input: NomSpan) -> IResult<NomSpan, bool, BindParserError> {
167 let true_ = value(true, tag("true"));
168 let false_ = value(false, tag("false"));
169 map_err(alt((true_, false_)), BindParserError::BoolLiteral).parse(input)
170}
171
172pub fn identifier(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
173 static RE: LazyLock<Regex> =
174 LazyLock::new(|| Regex::new(r"^[a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?").unwrap());
175 if let Some(mat) = RE.find(input.fragment()) {
176 Ok((
177 input.take_from(mat.end()),
178 input.take(mat.end()).take_from(mat.start()).fragment().to_string(),
179 ))
180 } else {
181 Err(nom::Err::Error(BindParserError::Identifier(input.fragment().to_string())))
182 }
183}
184
185pub fn compound_identifier(
186 input: NomSpan,
187) -> IResult<NomSpan, CompoundIdentifier, BindParserError> {
188 let (input, mut segments) = separated_list1(tag("."), identifier).parse(input)?;
189 let name = segments.pop().unwrap();
191 Ok((input, CompoundIdentifier { namespace: segments, name }))
192}
193
194pub fn condition_value(input: NomSpan) -> IResult<NomSpan, Value, BindParserError> {
195 let string = map(ws(string_literal), Value::StringLiteral);
196 let number = map(ws(numeric_literal), Value::NumericLiteral);
197 let boolean = map(ws(bool_literal), Value::BoolLiteral);
198 let identifer = map(ws(compound_identifier), Value::Identifier);
199
200 alt((string, number, boolean, identifer))
201 .parse(input)
202 .or_else(|_| Err(nom::Err::Error(BindParserError::ConditionValue(input.to_string()))))
203}
204
205pub fn using(input: NomSpan) -> IResult<NomSpan, Include, BindParserError> {
206 let as_keyword = ws(map_err(tag("as"), BindParserError::AsKeyword));
207 let (input, name) = ws(compound_identifier).parse(input)?;
208 let (input, alias) = opt(preceded(as_keyword, ws(identifier))).parse(input)?;
209 Ok((input, Include { name, alias }))
210}
211
212pub fn using_list(input: NomSpan) -> IResult<NomSpan, Vec<Include>, BindParserError> {
213 let using_keyword = ws(map_err(tag("using"), BindParserError::UsingKeyword));
214 let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
215 let using = delimited(using_keyword, ws(using), terminator);
216 many0(ws(using)).parse(input)
217}
218
219pub fn many_until_eof<'a, O, F>(
223 mut f: F,
224) -> impl Parser<NomSpan<'a>, Output = Vec<O>, Error = BindParserError>
225where
226 F: Parser<NomSpan<'a>, Output = O, Error = BindParserError>,
227{
228 move |mut input: NomSpan<'a>| {
229 let mut result = vec![];
230 loop {
231 if skip_ws(input)?.fragment().len() == 0 {
233 return Ok((skip_ws(input)?, result));
234 }
235
236 let (next_input, res) = f.parse(input)?;
237 if input.fragment().len() == next_input.fragment().len() {
238 panic!(
239 "many_until_eof called on an optional parser. This will result in an infinite loop"
240 );
241 }
242 input = next_input;
243 result.push(res);
244 }
245 }
246}
247
248pub fn ws<'a, O, F>(f: F) -> impl Parser<NomSpan<'a>, Output = O, Error = BindParserError>
252where
253 F: Parser<NomSpan<'a>, Output = O, Error = BindParserError>,
254{
255 preceded(comment_or_whitespace, f)
256}
257
258pub fn skip_ws(input: NomSpan) -> Result<NomSpan, nom::Err<BindParserError>> {
259 let (input, _) = comment_or_whitespace(input)?;
260 Ok(input)
261}
262
263fn comment_or_whitespace(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
264 let multispace = value((), multispace1);
265 value((), many0(alt((multispace, multiline_comment, singleline_comment)))).parse(input)
266}
267
268fn multiline_comment(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
270 let (input, _) = tag("/*").parse(input)?;
271 let mut iter = input.fragment().char_indices().peekable();
272 let mut stack = 1;
273 while let (Some((_, first)), Some((_, second))) = (iter.next(), iter.peek()) {
274 if first == '/' && *second == '*' {
275 stack += 1;
276 iter.next();
277 } else if first == '*' && *second == '/' {
278 stack -= 1;
279 iter.next();
280 if stack == 0 {
281 break;
282 }
283 }
284 }
285 if stack != 0 {
286 return Err(nom::Err::Failure(BindParserError::UnterminatedComment));
287 }
288
289 let consumed = if let Some((index, _)) = iter.peek() { *index } else { input.fragment().len() };
290 Ok((input.take_from(consumed), ()))
291}
292
293fn singleline_comment(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
295 value((), (tag("//"), not_line_ending, line_ending)).parse(input)
296}
297
298pub fn map_err<'a, O, P, G>(
300 mut parser: P,
301 f: G,
302) -> impl Parser<NomSpan<'a>, Output = O, Error = BindParserError>
303where
304 P: Parser<NomSpan<'a>, Output = O, Error = (NomSpan<'a>, ErrorKind)>,
305 G: Fn(String) -> BindParserError,
306{
307 move |input: NomSpan<'a>| {
308 parser.parse(input).map_err(|e| match e {
309 nom::Err::Error((i, _)) => nom::Err::Error(f(i.to_string())),
310 nom::Err::Failure((i, _)) => nom::Err::Failure(f(i.to_string())),
311 nom::Err::Incomplete(_) => {
312 unreachable!("Parser should never generate Incomplete errors")
313 }
314 })
315 }
316}
317
318#[macro_export]
319macro_rules! make_identifier{
320 ( $name:tt ) => {
321 {
322 CompoundIdentifier {
323 namespace: Vec::new(),
324 name: String::from($name),
325 }
326 }
327 };
328 ( $namespace:tt , $($rest:tt)* ) => {
329 {
330 let mut identifier = make_identifier!($($rest)*);
331 identifier.namespace.insert(0, String::from($namespace));
332 identifier
333 }
334 };
335}
336
337#[cfg(test)]
338pub mod test {
339 use super::*;
340
341 pub fn check_result<O: PartialEq + fmt::Debug>(
342 result: IResult<NomSpan, O, BindParserError>,
343 expected_input_fragment: &str,
344 expected_output: O,
345 ) {
346 match result {
347 Ok((input, output)) => {
348 assert_eq!(input.fragment(), &expected_input_fragment);
349 assert_eq!(output, expected_output);
350 }
351 Err(e) => {
352 panic!("{:#?}", e);
353 }
354 }
355 }
356
357 mod string_literal {
358 use super::*;
359
360 #[test]
361 fn basic() {
362 check_result(
364 string_literal(NomSpan::new(r#""abc 123"xyz"#)),
365 "xyz",
366 "abc 123".to_string(),
367 );
368 }
369
370 #[test]
371 fn match_once() {
372 check_result(
373 string_literal(NomSpan::new(r#""abc""123""#)),
374 r#""123""#,
375 "abc".to_string(),
376 );
377 }
378
379 #[test]
380 fn escaped() {
381 check_result(
382 string_literal(NomSpan::new(r#""abc \"esc\" xyz""#)),
383 "",
384 r#"abc \"esc\" xyz"#.to_string(),
385 );
386 }
387
388 #[test]
389 fn requires_quotations() {
390 assert_eq!(
391 string_literal(NomSpan::new(r#"abc"#)),
392 Err(nom::Err::Error(BindParserError::StringLiteral("abc".to_string())))
393 );
394 }
395
396 #[test]
397 fn empty() {
398 assert_eq!(
399 string_literal(NomSpan::new("")),
400 Err(nom::Err::Error(BindParserError::StringLiteral("".to_string())))
401 );
402 }
403 }
404
405 mod numeric_literals {
406 use super::*;
407
408 #[test]
409 fn decimal() {
410 check_result(numeric_literal(NomSpan::new("123")), "", 123);
411 }
412
413 #[test]
414 fn hex() {
415 check_result(numeric_literal(NomSpan::new("0x123")), "", 0x123);
416 check_result(numeric_literal(NomSpan::new("0xabcdef")), "", 0xabcdef);
417 check_result(numeric_literal(NomSpan::new("0xABCDEF")), "", 0xabcdef);
418 check_result(numeric_literal(NomSpan::new("0x123abc")), "", 0x123abc);
419
420 assert_eq!(
422 numeric_literal(NomSpan::new("abc123")),
423 Err(nom::Err::Error(BindParserError::NumericLiteral("abc123".to_string())))
424 );
425 check_result(numeric_literal(NomSpan::new("123abc")), "abc", 123);
426 }
427
428 #[test]
429 fn non_numbers() {
430 assert_eq!(
431 numeric_literal(NomSpan::new("xyz")),
432 Err(nom::Err::Error(BindParserError::NumericLiteral("xyz".to_string())))
433 );
434
435 assert_eq!(
437 numeric_literal(NomSpan::new("-1")),
438 Err(nom::Err::Error(BindParserError::NumericLiteral("-1".to_string())))
439 );
440 }
441
442 #[test]
443 fn overflow() {
444 check_result(
446 numeric_literal(NomSpan::new("18446744073709551615")),
447 "",
448 18446744073709551615,
449 );
450 check_result(
451 numeric_literal(NomSpan::new("0xffffffffffffffff")),
452 "",
453 18446744073709551615,
454 );
455 assert_eq!(
456 numeric_literal(NomSpan::new("18446744073709551616")),
457 Err(nom::Err::Error(BindParserError::NumericLiteral(
458 "18446744073709551616".to_string()
459 )))
460 );
461 check_result(
463 numeric_literal(NomSpan::new("0x10000000000000000")),
464 "x10000000000000000",
465 0,
466 );
467 }
468
469 #[test]
470 fn empty() {
471 assert_eq!(
473 numeric_literal(NomSpan::new("")),
474 Err(nom::Err::Error(BindParserError::NumericLiteral("".to_string())))
475 );
476 }
477 }
478
479 mod bool_literals {
480 use super::*;
481
482 #[test]
483 fn basic() {
484 check_result(bool_literal(NomSpan::new("true")), "", true);
485 check_result(bool_literal(NomSpan::new("false")), "", false);
486 }
487
488 #[test]
489 fn non_bools() {
490 assert_eq!(
492 bool_literal(NomSpan::new("tralse")),
493 Err(nom::Err::Error(BindParserError::BoolLiteral("tralse".to_string())))
494 );
495 }
496
497 #[test]
498 fn empty() {
499 assert_eq!(
501 bool_literal(NomSpan::new("")),
502 Err(nom::Err::Error(BindParserError::BoolLiteral("".to_string())))
503 );
504 }
505 }
506
507 mod identifiers {
508 use super::*;
509
510 #[test]
511 fn basic() {
512 check_result(identifier(NomSpan::new("abc_123_ABC")), "", "abc_123_ABC".to_string());
514
515 check_result(identifier(NomSpan::new("abc_123_ABC ")), " ", "abc_123_ABC".to_string());
517 check_result(identifier(NomSpan::new("abc_123_ABC;")), ";", "abc_123_ABC".to_string());
518 }
519
520 #[test]
521 fn invalid() {
522 assert_eq!(
524 identifier(NomSpan::new("_abc")),
525 Err(nom::Err::Error(BindParserError::Identifier("_abc".to_string())))
526 );
527
528 check_result(identifier(NomSpan::new("abc_")), "_", "abc".to_string());
530 }
531
532 #[test]
533 fn empty() {
534 assert_eq!(
536 identifier(NomSpan::new("")),
537 Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
538 );
539 }
540 }
541
542 mod compound_identifiers {
543 use super::*;
544
545 #[test]
546 fn single_identifier() {
547 check_result(
549 compound_identifier(NomSpan::new("abc_123_ABC")),
550 "",
551 make_identifier!["abc_123_ABC"],
552 );
553 }
554
555 #[test]
556 fn multiple_identifiers() {
557 check_result(
559 compound_identifier(NomSpan::new("abc.def")),
560 "",
561 make_identifier!["abc", "def"],
562 );
563 check_result(
564 compound_identifier(NomSpan::new("abc.def.ghi")),
565 "",
566 make_identifier!["abc", "def", "ghi"],
567 );
568 }
569
570 #[test]
571 fn empty() {
572 assert_eq!(
574 compound_identifier(NomSpan::new(".")),
575 Err(nom::Err::Error(BindParserError::Identifier(".".to_string())))
576 );
577 assert_eq!(
578 compound_identifier(NomSpan::new(".abc")),
579 Err(nom::Err::Error(BindParserError::Identifier(".abc".to_string())))
580 );
581 check_result(
582 compound_identifier(NomSpan::new("abc..def")),
583 "..def",
584 make_identifier!["abc"],
585 );
586 check_result(compound_identifier(NomSpan::new("abc.")), ".", make_identifier!["abc"]);
587
588 assert_eq!(
590 compound_identifier(NomSpan::new("")),
591 Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
592 );
593 }
594 }
595
596 mod condition_values {
597 use super::*;
598
599 #[test]
600 fn string() {
601 check_result(
602 condition_value(NomSpan::new(r#""abc""#)),
603 "",
604 Value::StringLiteral("abc".to_string()),
605 );
606 }
607
608 #[test]
609 fn bool() {
610 check_result(condition_value(NomSpan::new("true")), "", Value::BoolLiteral(true));
611 }
612
613 #[test]
614 fn number() {
615 check_result(condition_value(NomSpan::new("123")), "", Value::NumericLiteral(123));
616 }
617
618 #[test]
619 fn identifier() {
620 check_result(
621 condition_value(NomSpan::new("abc")),
622 "",
623 Value::Identifier(make_identifier!["abc"]),
624 );
625 }
626
627 #[test]
628 fn empty() {
629 assert_eq!(
631 condition_value(NomSpan::new("")),
632 Err(nom::Err::Error(BindParserError::ConditionValue("".to_string())))
633 );
634 }
635 }
636
637 mod using_lists {
638 use super::*;
639
640 #[test]
641 fn one_include() {
642 check_result(
643 using_list(NomSpan::new("using test;")),
644 "",
645 vec![Include { name: make_identifier!["test"], alias: None }],
646 );
647 }
648
649 #[test]
650 fn multiple_includes() {
651 check_result(
652 using_list(NomSpan::new("using abc;using def;")),
653 "",
654 vec![
655 Include { name: make_identifier!["abc"], alias: None },
656 Include { name: make_identifier!["def"], alias: None },
657 ],
658 );
659 check_result(
660 using_list(NomSpan::new("using abc;using def;using ghi;")),
661 "",
662 vec![
663 Include { name: make_identifier!["abc"], alias: None },
664 Include { name: make_identifier!["def"], alias: None },
665 Include { name: make_identifier!["ghi"], alias: None },
666 ],
667 );
668 }
669
670 #[test]
671 fn compound_identifiers() {
672 check_result(
673 using_list(NomSpan::new("using abc.def;")),
674 "",
675 vec![Include { name: make_identifier!["abc", "def"], alias: None }],
676 );
677 }
678
679 #[test]
680 fn aliases() {
681 check_result(
682 using_list(NomSpan::new("using abc.def as one;using ghi as two;")),
683 "",
684 vec![
685 Include {
686 name: make_identifier!["abc", "def"],
687 alias: Some("one".to_string()),
688 },
689 Include { name: make_identifier!["ghi"], alias: Some("two".to_string()) },
690 ],
691 );
692 }
693
694 #[test]
695 fn whitespace() {
696 check_result(
697 using_list(NomSpan::new(" using abc\t as one ;\n using def ; ")),
698 " ",
699 vec![
700 Include { name: make_identifier!["abc"], alias: Some("one".to_string()) },
701 Include { name: make_identifier!["def"], alias: None },
702 ],
703 );
704 check_result(
705 using_list(NomSpan::new("usingabc;")),
706 "",
707 vec![Include { name: make_identifier!["abc"], alias: None }],
708 );
709 }
710
711 #[test]
712 fn invalid() {
713 check_result(using_list(NomSpan::new("using abc")), "using abc", vec![]);
715 }
716
717 #[test]
718 fn empty() {
719 check_result(using_list(NomSpan::new("")), "", vec![]);
720 }
721 }
722
723 mod whitespace {
724 use super::*;
725
726 #[test]
727 fn multiline_comments() {
728 check_result(multiline_comment(NomSpan::new("/*one*/")), "", ());
729 check_result(multiline_comment(NomSpan::new("/*one*/two")), "two", ());
730 check_result(
731 multiline_comment(NomSpan::new("/*one/*two*/three/*four*/five*/six")),
732 "six",
733 (),
734 );
735 check_result(multiline_comment(NomSpan::new("/*/*one*/*/two")), "two", ());
736 assert_eq!(
737 multiline_comment(NomSpan::new("/*one")),
738 Err(nom::Err::Failure(BindParserError::UnterminatedComment))
739 );
740
741 let (input, _) = multiline_comment(NomSpan::new("/*\n/*one\n*/*/two")).unwrap();
743 assert_eq!(input.location_offset(), 13);
744 assert_eq!(input.location_line(), 3);
745 assert_eq!(input.fragment(), &"two");
746
747 let (input, _) = multiline_comment(NomSpan::new("/*\n/*one\n*/*/")).unwrap();
748 assert_eq!(input.location_offset(), 13);
749 assert_eq!(input.location_line(), 3);
750 assert_eq!(input.fragment(), &"");
751 }
752
753 #[test]
754 fn singleline_comments() {
755 check_result(singleline_comment(NomSpan::new("//one\ntwo")), "two", ());
756 check_result(singleline_comment(NomSpan::new("//one\r\ntwo")), "two", ());
757 }
758
759 #[test]
760 fn whitespace() {
761 let test = || map(tag("test"), |s: NomSpan| s.fragment().to_string());
762 check_result(ws(test()).parse(NomSpan::new("test")), "", "test".to_string());
763
764 check_result(ws(test()).parse(NomSpan::new(" \n\t\r\ntest")), "", "test".to_string());
765 check_result(
766 ws(test()).parse(NomSpan::new("test \n\t\r\n")),
767 " \n\t\r\n",
768 "test".to_string(),
769 );
770
771 check_result(
772 ws(test()).parse(NomSpan::new(" // test \n test // test \n ")),
773 " // test \n ",
774 "test".to_string(),
775 );
776
777 check_result(
778 ws(test()).parse(NomSpan::new(" /* test */ test /* test */ ")),
779 " /* test */ ",
780 "test".to_string(),
781 );
782 }
783
784 #[test]
785 fn skip_whitespace() {
786 let result = skip_ws(NomSpan::new("test")).unwrap();
787 assert_eq!(result.location_offset(), 0);
788 assert_eq!(result.location_line(), 1);
789 assert_eq!(result.fragment(), &"test");
790
791 let result = skip_ws(NomSpan::new(" \n\t\r\ntest \n\t\r\n")).unwrap();
792 assert_eq!(result.location_offset(), 5);
793 assert_eq!(result.location_line(), 3);
794 assert_eq!(result.fragment(), &"test \n\t\r\n");
795
796 let result = skip_ws(NomSpan::new(" // test \n test // test \n ")).unwrap();
797 assert_eq!(result.location_offset(), 11);
798 assert_eq!(result.location_line(), 2);
799 assert_eq!(result.fragment(), &"test // test \n ");
800
801 let result = skip_ws(NomSpan::new(" /* test */ test /* test */ ")).unwrap();
802 assert_eq!(result.location_offset(), 12);
803 assert_eq!(result.location_line(), 1);
804 assert_eq!(result.fragment(), &"test /* test */ ");
805 }
806 }
807}