bind/parser/
common.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    // Note: When the base16 parser fails but input starts with '0x' this will succeed and return 0.
162    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    // Segments must be nonempty, so it's safe to pop off the name.
189    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
218/// Applies the parser `f` until reaching the end of the input. `f` must always make progress (i.e.
219/// consume input) and many_until_eof will panic if it doesn't, to prevent infinite loops. Returns
220/// the results of `f` in a Vec.
221pub 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            // Ignore trailing whitespace at the end of the file.
231            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
247/// Wraps a parser |f| and discards zero or more whitespace characters or comments before it.
248/// Doesn't discard whitespace after the parser, since this would make it difficult to ensure that
249/// the AST spans contain no trailing whitespace.
250pub 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
267/// Parser that matches a multiline comment, e.g. "/* comment */". Comments may be nested.
268fn 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
292/// Parser that matches a single line comment, e.g. "// comment\n".
293fn singleline_comment(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
294    value((), (tag("//"), not_line_ending, line_ending)).parse(input)
295}
296
297// Wraps a parser and replaces its error.
298pub 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            // Matches a string literal, leaves correct tail.
362            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            // Does not match hex without '0x' prefix.
420            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            // Does not match negative numbers (for now).
435            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            // Does not match numbers larger than u64.
444            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            // Note: This is matching '0' from '0x' but failing to parse the entire string.
461            check_result(
462                numeric_literal(NomSpan::new("0x10000000000000000")),
463                "x10000000000000000",
464                0,
465            );
466        }
467
468        #[test]
469        fn empty() {
470            // Does not match an empty string.
471            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            // Does not match anything else.
490            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            // Does not match an empty string.
499            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            // Matches identifiers with lowercase, uppercase, digits, and underscores.
512            check_result(identifier(NomSpan::new("abc_123_ABC")), "", "abc_123_ABC".to_string());
513
514            // Match is terminated by whitespace or punctuation.
515            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            // Does not match an identifier beginning or ending with '_'.
522            assert_eq!(
523                identifier(NomSpan::new("_abc")),
524                Err(nom::Err::Error(BindParserError::Identifier("_abc".to_string())))
525            );
526
527            // Note: Matches up until the '_' but fails to parse the entire string.
528            check_result(identifier(NomSpan::new("abc_")), "_", "abc".to_string());
529        }
530
531        #[test]
532        fn empty() {
533            // Does not match an empty string.
534            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            // Matches single identifier.
547            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            // Matches compound identifiers.
557            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            // Does not match empty identifiers.
572            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            // Does not match an empty string.
588            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            // Does not match empty string.
629            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            // Must be followed by ';'.
713            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            // Span is updated correctly.
741            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}