Skip to main content

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_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    // Note: When the base16 parser fails but input starts with '0x' this will succeed and return 0.
163    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    // Segments must be nonempty, so it's safe to pop off the name.
190    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
219/// Applies the parser `f` until reaching the end of the input. `f` must always make progress (i.e.
220/// consume input) and many_until_eof will panic if it doesn't, to prevent infinite loops. Returns
221/// the results of `f` in a Vec.
222pub 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            // Ignore trailing whitespace at the end of the file.
232            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
248/// Wraps a parser |f| and discards zero or more whitespace characters or comments before it.
249/// Doesn't discard whitespace after the parser, since this would make it difficult to ensure that
250/// the AST spans contain no trailing whitespace.
251pub 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
268/// Parser that matches a multiline comment, e.g. "/* comment */". Comments may be nested.
269fn 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
293/// Parser that matches a single line comment, e.g. "// comment\n".
294fn singleline_comment(input: NomSpan) -> IResult<NomSpan, (), BindParserError> {
295    value((), (tag("//"), not_line_ending, line_ending)).parse(input)
296}
297
298// Wraps a parser and replaces its error.
299pub 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            // Matches a string literal, leaves correct tail.
363            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            // Does not match hex without '0x' prefix.
421            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            // Does not match negative numbers (for now).
436            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            // Does not match numbers larger than u64.
445            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            // Note: This is matching '0' from '0x' but failing to parse the entire string.
462            check_result(
463                numeric_literal(NomSpan::new("0x10000000000000000")),
464                "x10000000000000000",
465                0,
466            );
467        }
468
469        #[test]
470        fn empty() {
471            // Does not match an empty string.
472            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            // Does not match anything else.
491            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            // Does not match an empty string.
500            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            // Matches identifiers with lowercase, uppercase, digits, and underscores.
513            check_result(identifier(NomSpan::new("abc_123_ABC")), "", "abc_123_ABC".to_string());
514
515            // Match is terminated by whitespace or punctuation.
516            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            // Does not match an identifier beginning or ending with '_'.
523            assert_eq!(
524                identifier(NomSpan::new("_abc")),
525                Err(nom::Err::Error(BindParserError::Identifier("_abc".to_string())))
526            );
527
528            // Note: Matches up until the '_' but fails to parse the entire string.
529            check_result(identifier(NomSpan::new("abc_")), "_", "abc".to_string());
530        }
531
532        #[test]
533        fn empty() {
534            // Does not match an empty string.
535            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            // Matches single identifier.
548            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            // Matches compound identifiers.
558            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            // Does not match empty identifiers.
573            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            // Does not match an empty string.
589            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            // Does not match empty string.
630            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            // Must be followed by ';'.
714            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            // Span is updated correctly.
742            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}