1614 lines
74 KiB
Rust
1614 lines
74 KiB
Rust
use std::borrow::Cow;
|
|
|
|
use super::ast::*;
|
|
use winnow::{Parser, combinator::trace, error::ParserError};
|
|
|
|
impl<'a> AnyString<'a> {
|
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{combinator::*, prelude::*, token::*};
|
|
|
|
let quote = alt((
|
|
'`'.value(QuoteType::Backtick),
|
|
'\''.value(QuoteType::Single),
|
|
'\"'.value(QuoteType::Double),
|
|
));
|
|
|
|
macro_rules! surrounding {
|
|
() => {
|
|
take_while(0.., |b: char| {
|
|
!b.is_whitespace() && b.is_alphabetic() && !['\'', '"', '`', '#'].contains(&b)
|
|
})
|
|
};
|
|
}
|
|
|
|
let preamble = (
|
|
surrounding!(),
|
|
take_while(0.., |c| c == '#').map(|i: &'a str| i.len()),
|
|
quote,
|
|
);
|
|
|
|
trace(
|
|
"string",
|
|
preamble.flat_map(
|
|
|(prefix, num_hashtags, quote): (&'a str, usize, QuoteType)| {
|
|
let end = (
|
|
match quote {
|
|
QuoteType::Single => '\'',
|
|
QuoteType::Double => '\"',
|
|
QuoteType::Backtick => '`',
|
|
},
|
|
repeat::<_, _, Cow<'a, str>, _, _>(
|
|
num_hashtags..=num_hashtags,
|
|
literal("#"),
|
|
),
|
|
);
|
|
|
|
let contents = repeat_till(0.., any, end).map(|(contents, _)| contents);
|
|
|
|
(contents, surrounding!()).map(
|
|
move |(contents, suffix): (Cow<'a, str>, &'a str)| Self {
|
|
prefix: prefix.into(),
|
|
ty: quote,
|
|
contents,
|
|
num_hashtags,
|
|
suffix: suffix.into(),
|
|
},
|
|
)
|
|
},
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> Space<'a> {
|
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{prelude::*, token::*};
|
|
|
|
trace(
|
|
"space",
|
|
take_while(0.., |b: char| b.is_whitespace()).map(|i: &'a str| Self(i.into())),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl PathSep {
|
|
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{combinator::*, prelude::*};
|
|
|
|
trace(
|
|
"pathsep",
|
|
alt(('/'.value(Self::Slash), '\\'.value(Self::Backslash))),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Separator {
|
|
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{combinator::*, prelude::*, token::*};
|
|
|
|
trace(
|
|
"separator",
|
|
alt((
|
|
"::".value(Self::DoubleColon),
|
|
(literal('=')).value(Self::Eq),
|
|
(literal(':')).value(Self::Colon),
|
|
)),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> FileLocation<'a> {
|
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{ascii::dec_uint, combinator::*, prelude::*};
|
|
|
|
let colon_number = || {
|
|
(":".value(()), dec_uint::<_, u64, _>.take())
|
|
.map(|(_, number): (_, &str)| Cow::Borrowed(number))
|
|
};
|
|
let line_offset = (colon_number(), opt(colon_number()));
|
|
|
|
trace(
|
|
"file location",
|
|
line_offset.map(|(line, offset)| FileLocation { line, offset }),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> FileName<'a> {
|
|
fn parse(segment: PathSegment<'a>, location: Option<FileLocation<'a>>) -> Self {
|
|
fn rsplit<'a>(
|
|
input: Cow<'a, str>,
|
|
delimiter: char,
|
|
) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
|
|
match input {
|
|
Cow::Borrowed(s) => s
|
|
.rsplit_once(delimiter)
|
|
.map(|(a, b)| (Cow::Borrowed(a), Cow::Borrowed(b))),
|
|
Cow::Owned(s) => s
|
|
.rsplit_once(delimiter)
|
|
.map(|(a, b)| (Cow::Owned(a.to_string()), Cow::Owned(b.to_string()))),
|
|
}
|
|
}
|
|
|
|
let (new_segment, ext_excluding_dot) = if let Some((segment, ext_excluding_dot)) =
|
|
rsplit(segment.segment.clone(), '.')
|
|
&& !ext_excluding_dot.is_empty()
|
|
{
|
|
(segment, Some(ext_excluding_dot))
|
|
} else {
|
|
(segment.segment, None)
|
|
};
|
|
|
|
Self {
|
|
leading_separator: segment.leading_separator,
|
|
segment: new_segment,
|
|
ext_excluding_dot,
|
|
location,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Path<'a> {
|
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{combinator::*, prelude::*, token::*};
|
|
|
|
let terminator = || {
|
|
alt((
|
|
eof.value(()),
|
|
any::<&'a str, E>
|
|
.verify(|i: &char| {
|
|
(*i).is_whitespace()
|
|
|| !(i.is_alphanumeric()
|
|
|| ['_', '-', '\"', '\'', '.', '/', '\\'].contains(i))
|
|
})
|
|
.value(()),
|
|
))
|
|
};
|
|
|
|
let terminator_or_sep = || alt((PathSep::parse().value(()), terminator()));
|
|
|
|
let till_next_sep = || {
|
|
trace(
|
|
"till next sep",
|
|
repeat_till(0.., any::<&'a str, E>, peek(terminator_or_sep()))
|
|
.map(|(segment, _)| segment),
|
|
)
|
|
};
|
|
|
|
let sep_and_next =
|
|
(PathSep::parse(), till_next_sep()).map(|(leading_separator, segment)| PathSegment {
|
|
leading_separator,
|
|
segment,
|
|
});
|
|
let opt_sep_and_next =
|
|
(opt(PathSep::parse()), till_next_sep()).map(|(leading_separator, segment)| {
|
|
PathSegment {
|
|
leading_separator: leading_separator.unwrap_or(PathSep::None),
|
|
segment,
|
|
}
|
|
});
|
|
|
|
let drive = opt((
|
|
any::<&'a str, E>.verify(|x: &char| x.is_ascii_alphabetic()),
|
|
':',
|
|
))
|
|
.map(|i| i.map(|(letter, _): (char, char)| letter));
|
|
let drive_and_segments = (
|
|
drive,
|
|
opt_sep_and_next,
|
|
repeat_till(0.., sep_and_next, peek(terminator()))
|
|
.map(|(segments, _): (Vec<PathSegment>, _)| segments),
|
|
opt(FileLocation::parse()),
|
|
);
|
|
|
|
trace(
|
|
"path",
|
|
drive_and_segments
|
|
.map(|(drive, segment, segments, location)| {
|
|
let (segments, last) = {
|
|
let mut segments = segments;
|
|
segments.insert(0, segment);
|
|
let last = segments.pop().unwrap();
|
|
(segments, last)
|
|
};
|
|
|
|
let filename = FileName::parse(last, location);
|
|
|
|
Self {
|
|
drive_excluding_colon: drive,
|
|
segments,
|
|
filename,
|
|
}
|
|
})
|
|
.verify(|i| {
|
|
// just ".." isn't valid
|
|
if i.segments.is_empty()
|
|
&& (i.filename.segment == ".." || i.filename.segment == ".")
|
|
{
|
|
return false;
|
|
}
|
|
|
|
!i.segments.is_empty()
|
|
|| i.filename.ext_excluding_dot.is_some()
|
|
|| !matches!(i.filename.leading_separator, PathSep::None)
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> Atom<'a> {
|
|
fn parse<E: ParserError<&'a str>, T: 'a, P: Parser<&'a str, T, E>>(
|
|
terminated_by: impl (Fn() -> P) + 'a,
|
|
) -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{combinator::*, prelude::*, token::*};
|
|
|
|
let text = (
|
|
not(peek(terminated_by())),
|
|
repeat_till::<_, _, Cow<'a, str>, _, _, _, _>(
|
|
1..,
|
|
any::<&str, _>,
|
|
peek(alt((
|
|
terminated_by().value(()),
|
|
any::<&str, _>.verify(|i| i.is_whitespace()).value(()),
|
|
eof::<&str, _>.value(()),
|
|
))),
|
|
)
|
|
.map(|(i, _)| Self::Text(i)),
|
|
)
|
|
.map(|(_, x)| x);
|
|
|
|
trace("atom", alt((text,)))
|
|
}
|
|
}
|
|
|
|
impl<'a> Token<'a> {
|
|
fn parse_without_separator<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{combinator::*, prelude::*, token::*};
|
|
|
|
let delimited: Box<dyn Parser<&'a str, Self, E>> =
|
|
Box::new(Delimited::parse().map(Self::Delimited));
|
|
|
|
let lifetime = (
|
|
"'",
|
|
alt((
|
|
repeat::<_, _, Cow<'a, str>, _, _>(
|
|
1..=3,
|
|
any.verify(|i: &char| i.is_ascii_lowercase() || *i == '_'),
|
|
)
|
|
.take(),
|
|
"{erased}",
|
|
)),
|
|
peek(alt((
|
|
any::<&str, _>
|
|
.verify(|i: &char| !i.is_alphabetic() && *i != '\'')
|
|
.value(()),
|
|
eof::<&str, _>.value(()),
|
|
))),
|
|
)
|
|
.map(|(_, lifetime, _)| Token::Lifetime(lifetime.into()));
|
|
|
|
trace(
|
|
"token-without-sep",
|
|
alt((
|
|
"true".value(Self::True),
|
|
"false".value(Self::False),
|
|
"None".value(Self::None),
|
|
Number::parse().map(Self::Number),
|
|
Path::parse().map(Self::Path),
|
|
lifetime,
|
|
AnyString::parse().map(Self::String),
|
|
delimited,
|
|
Atom::parse(|| {
|
|
alt((
|
|
Separator::parse().value(""),
|
|
alt((")", "]", "}", ">")),
|
|
alt(("(", "[", "{", "<")),
|
|
alt(("`", "'", "\"")),
|
|
))
|
|
})
|
|
.map(Self::Atom),
|
|
)),
|
|
)
|
|
}
|
|
|
|
fn parse<E: ParserError<&'a str> + 'a>() -> Box<dyn Parser<&'a str, Self, E> + 'a> {
|
|
use winnow::{combinator::*, prelude::*};
|
|
|
|
let before = Self::parse_without_separator();
|
|
|
|
Box::new(trace(
|
|
"token",
|
|
alt((
|
|
(
|
|
before,
|
|
opt(
|
|
(Space::parse(), Separator::parse()).flat_map(|(space, sep)| {
|
|
let box_dyn_segment: Box<dyn Parser<_, _, _>> =
|
|
Box::new(Segment::parse());
|
|
box_dyn_segment.map(move |segment| (space.clone(), sep, segment))
|
|
}),
|
|
),
|
|
)
|
|
.map(|(before, trailer)| {
|
|
if let Some((space_before, separator, after)) = trailer {
|
|
Token::Separated {
|
|
before: Box::new(before),
|
|
space_before,
|
|
separator,
|
|
after: Box::new(after),
|
|
}
|
|
} else {
|
|
before
|
|
}
|
|
}),
|
|
Atom::parse(|| fail::<_, (), _>).map(Self::Atom),
|
|
)),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl<'a> Delimited<'a> {
|
|
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{combinator::*, prelude::*, token::*};
|
|
|
|
trace(
|
|
"delimited",
|
|
(
|
|
opt((
|
|
Atom::parse(|| {
|
|
alt((
|
|
Separator::parse().value(""),
|
|
alt((")", "]", "}", ">")),
|
|
alt(("(", "[", "{", "<")),
|
|
alt(("`", "'", "\"")),
|
|
))
|
|
}),
|
|
Space::parse(),
|
|
)),
|
|
alt((
|
|
literal('(').map(|_| literal(')').value(Delimiter::Paren)),
|
|
literal('[').map(|_| literal(']').value(Delimiter::Bracket)),
|
|
literal('{').map(|_| literal('}').value(Delimiter::Brace)),
|
|
literal('<').map(|_| literal('>').value(Delimiter::Angle)),
|
|
))
|
|
.flat_map(|end| Segments::parse(end)),
|
|
)
|
|
.map(|(prefix, (contents, delimiter))| Self {
|
|
prefix,
|
|
delimiter,
|
|
contents,
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> Segments<'a> {
|
|
fn parse<E: ParserError<&'a str> + 'a, End: 'a>(
|
|
end: impl Parser<&'a str, End, E> + 'a,
|
|
) -> Box<dyn Parser<&'a str, (Self, End), E> + 'a> {
|
|
use winnow::{combinator::*, prelude::*};
|
|
|
|
Box::new(trace(
|
|
"segments",
|
|
repeat_till(0.., Segment::parse(), (Space::parse(), end)).map(
|
|
|(segments, (trailing_space, end)): (Vec<_>, _)| {
|
|
(
|
|
Self {
|
|
segments,
|
|
trailing_space,
|
|
},
|
|
end,
|
|
)
|
|
},
|
|
),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl<'a> Number<'a> {
|
|
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::{ascii::*, combinator::*, prelude::*, token::*};
|
|
|
|
trace(
|
|
"number",
|
|
(
|
|
alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take())),
|
|
opt((
|
|
"_",
|
|
repeat::<_, _, Cow<'a, str>, _, _>(
|
|
1..,
|
|
any::<&'a str, E>.verify(|x| x.is_alphanumeric()),
|
|
)
|
|
.take(),
|
|
)
|
|
.map(|(_, suffix): (_, &str)| suffix)),
|
|
peek(not(any::<&'a str, E>.verify(|x: &char| x.is_alphabetic()))),
|
|
)
|
|
.map(|(number, suffix, _): (&str, _, _)| Self {
|
|
number: number.into(),
|
|
suffix_without_underscore: suffix.map(Into::into),
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> Segment<'a> {
|
|
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
|
|
use winnow::prelude::*;
|
|
|
|
trace(
|
|
"segment",
|
|
(Space::parse(), Token::parse()).map(|(leading_space, token)| Self {
|
|
leading_space,
|
|
token,
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Parses an input string (a log line) into an ast.
|
|
///
|
|
/// This *should* never error.
|
|
/// Many tests ensure that arbitrary input can be parsed.
|
|
/// Even if non-structured or completely random.
|
|
/// The parser will gracefully accept such strings anyway, and just categorize them suboptimally.
|
|
pub fn parse_input<'a>(i: &'a str) -> Result<Segments<'a>, String> {
|
|
use winnow::combinator::eof;
|
|
Segments::parse(eof::<&str, winnow::error::EmptyError>)
|
|
.map(|(segments, _)| segments)
|
|
.parse(i)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use insta::assert_debug_snapshot;
|
|
use winnow::Parser;
|
|
|
|
use crate::{
|
|
ast::{Path, Segments},
|
|
parse_input,
|
|
};
|
|
|
|
fn parse_path_only<'a>(i: &'a str) -> Path<'a> {
|
|
Path::parse::<winnow::error::EmptyError>().parse(i).unwrap()
|
|
}
|
|
|
|
fn parse<'a>(input: &'a str) -> Segments<'a> {
|
|
parse_input(input).unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn parse_twodots() {
|
|
assert_debug_snapshot!(parse(r#".."#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
"..",
|
|
),
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_dot() {
|
|
assert_debug_snapshot!(parse(r#"."#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
".",
|
|
),
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_parent() {
|
|
assert_debug_snapshot!(parse_path_only(r#"../foo.rs"#), @r#"
|
|
Path {
|
|
drive_excluding_colon: None,
|
|
segments: [
|
|
PathSegment {
|
|
leading_separator: None,
|
|
segment: "..",
|
|
},
|
|
],
|
|
filename: FileName {
|
|
leading_separator: Slash,
|
|
segment: "foo",
|
|
ext_excluding_dot: Some(
|
|
"rs",
|
|
),
|
|
location: None,
|
|
},
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_cwd() {
|
|
assert_debug_snapshot!(parse_path_only(r#"./foo.rs"#), @r#"
|
|
Path {
|
|
drive_excluding_colon: None,
|
|
segments: [
|
|
PathSegment {
|
|
leading_separator: None,
|
|
segment: ".",
|
|
},
|
|
],
|
|
filename: FileName {
|
|
leading_separator: Slash,
|
|
segment: "foo",
|
|
ext_excluding_dot: Some(
|
|
"rs",
|
|
),
|
|
location: None,
|
|
},
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_cwd_in_path() {
|
|
assert_debug_snapshot!(parse_path_only(r#"foo/./foo.rs"#), @r#"
|
|
Path {
|
|
drive_excluding_colon: None,
|
|
segments: [
|
|
PathSegment {
|
|
leading_separator: None,
|
|
segment: "foo",
|
|
},
|
|
PathSegment {
|
|
leading_separator: Slash,
|
|
segment: ".",
|
|
},
|
|
],
|
|
filename: FileName {
|
|
leading_separator: Slash,
|
|
segment: "foo",
|
|
ext_excluding_dot: Some(
|
|
"rs",
|
|
),
|
|
location: None,
|
|
},
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_parent_in_path() {
|
|
assert_debug_snapshot!(parse_path_only(r#"foo/../foo.rs"#), @r#"
|
|
Path {
|
|
drive_excluding_colon: None,
|
|
segments: [
|
|
PathSegment {
|
|
leading_separator: None,
|
|
segment: "foo",
|
|
},
|
|
PathSegment {
|
|
leading_separator: Slash,
|
|
segment: "..",
|
|
},
|
|
],
|
|
filename: FileName {
|
|
leading_separator: Slash,
|
|
segment: "foo",
|
|
ext_excluding_dot: Some(
|
|
"rs",
|
|
),
|
|
location: None,
|
|
},
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_path() {
|
|
assert_debug_snapshot!(parse_path_only(r#"tests/ui/impl-trait/unsized_coercion.rs"#), @r#"
|
|
Path {
|
|
drive_excluding_colon: None,
|
|
segments: [
|
|
PathSegment {
|
|
leading_separator: None,
|
|
segment: "tests",
|
|
},
|
|
PathSegment {
|
|
leading_separator: Slash,
|
|
segment: "ui",
|
|
},
|
|
PathSegment {
|
|
leading_separator: Slash,
|
|
segment: "impl-trait",
|
|
},
|
|
],
|
|
filename: FileName {
|
|
leading_separator: Slash,
|
|
segment: "unsized_coercion",
|
|
ext_excluding_dot: Some(
|
|
"rs",
|
|
),
|
|
location: None,
|
|
},
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_path_with_file_line() {
|
|
assert_debug_snapshot!(parse_path_only(r#"tests/ui/impl-trait/unsized_coercion.rs:3:4"#), @r#"
|
|
Path {
|
|
drive_excluding_colon: None,
|
|
segments: [
|
|
PathSegment {
|
|
leading_separator: None,
|
|
segment: "tests",
|
|
},
|
|
PathSegment {
|
|
leading_separator: Slash,
|
|
segment: "ui",
|
|
},
|
|
PathSegment {
|
|
leading_separator: Slash,
|
|
segment: "impl-trait",
|
|
},
|
|
],
|
|
filename: FileName {
|
|
leading_separator: Slash,
|
|
segment: "unsized_coercion",
|
|
ext_excluding_dot: Some(
|
|
"rs",
|
|
),
|
|
location: Some(
|
|
FileLocation {
|
|
line: "3",
|
|
offset: Some(
|
|
"4",
|
|
),
|
|
},
|
|
),
|
|
},
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_empty() {
|
|
assert_debug_snapshot!(parse(r#""#), @r#"
|
|
Segments {
|
|
segments: [],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#)
|
|
}
|
|
|
|
#[test]
|
|
fn parse_delimited_separated() {
|
|
assert_debug_snapshot!(parse(r#"a = Struct { b = 3 }"#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"a",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
" ",
|
|
),
|
|
separator: Eq,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: Some(
|
|
(
|
|
Text(
|
|
"Struct",
|
|
),
|
|
Space(
|
|
" ",
|
|
),
|
|
),
|
|
),
|
|
delimiter: Brace,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"b",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
" ",
|
|
),
|
|
separator: Eq,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Number(
|
|
Number {
|
|
number: "3",
|
|
suffix_without_underscore: None,
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
" ",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#)
|
|
}
|
|
|
|
#[test]
|
|
fn parse_text() {
|
|
assert_debug_snapshot!(parse(r#"abc"#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
"abc",
|
|
),
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#)
|
|
}
|
|
|
|
#[test]
|
|
fn parse_boolean() {
|
|
assert_debug_snapshot!(parse(r#"true"#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: True,
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r#"false"#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: False,
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_string() {
|
|
assert_debug_snapshot!(parse(r##""foo""##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "",
|
|
ty: Double,
|
|
contents: "foo",
|
|
num_hashtags: 0,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"#"foo"#"##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "",
|
|
ty: Double,
|
|
contents: "foo",
|
|
num_hashtags: 1,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"r#"foo"#"##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "r",
|
|
ty: Double,
|
|
contents: "foo",
|
|
num_hashtags: 1,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"c"foo""##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "c",
|
|
ty: Double,
|
|
contents: "foo",
|
|
num_hashtags: 0,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"b"foo""##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "b",
|
|
ty: Double,
|
|
contents: "foo",
|
|
num_hashtags: 0,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"'a'"##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "",
|
|
ty: Single,
|
|
contents: "a",
|
|
num_hashtags: 0,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"`b`"##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "",
|
|
ty: Backtick,
|
|
contents: "b",
|
|
num_hashtags: 0,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"b'foo'"##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "b",
|
|
ty: Single,
|
|
contents: "foo",
|
|
num_hashtags: 0,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
assert_debug_snapshot!(parse(r##"b`foo`"##), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: String(
|
|
AnyString {
|
|
prefix: "b",
|
|
ty: Backtick,
|
|
contents: "foo",
|
|
num_hashtags: 0,
|
|
suffix: "",
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_ex1() {
|
|
assert_debug_snapshot!(parse(r#"a::<b::c::Generalizer<'_, '_>>(a = 3_usize, b = 3_usize)"#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"a",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: None,
|
|
delimiter: Angle,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"b",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"c",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: Some(
|
|
(
|
|
Text(
|
|
"Generalizer",
|
|
),
|
|
Space(
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
delimiter: Angle,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Lifetime(
|
|
"_",
|
|
),
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
",",
|
|
),
|
|
),
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Lifetime(
|
|
"_",
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: None,
|
|
delimiter: Paren,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"a",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
" ",
|
|
),
|
|
separator: Eq,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Number(
|
|
Number {
|
|
number: "3",
|
|
suffix_without_underscore: Some(
|
|
"usize",
|
|
),
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
",",
|
|
),
|
|
),
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"b",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
" ",
|
|
),
|
|
separator: Eq,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Number(
|
|
Number {
|
|
number: "3",
|
|
suffix_without_underscore: Some(
|
|
"usize",
|
|
),
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_ex2() {
|
|
assert_debug_snapshot!(parse(r#"super_combine_consts::<rustc_type_ir::relate::solver_relating::SolverRelating<'_, rustc_infer::infer::InferCtxt<'_>, rustc_middle::ty::context::TyCtxt<'_>>>(?1c, ?2c)"#), @r#"
|
|
Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"super_combine_consts",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: None,
|
|
delimiter: Angle,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"rustc_type_ir",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"relate",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"solver_relating",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: Some(
|
|
(
|
|
Text(
|
|
"SolverRelating",
|
|
),
|
|
Space(
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
delimiter: Angle,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Lifetime(
|
|
"_",
|
|
),
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
",",
|
|
),
|
|
),
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"rustc_infer",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"infer",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: Some(
|
|
(
|
|
Text(
|
|
"InferCtxt",
|
|
),
|
|
Space(
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
delimiter: Angle,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Lifetime(
|
|
"_",
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
",",
|
|
),
|
|
),
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"rustc_middle",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"ty",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Separated {
|
|
before: Atom(
|
|
Text(
|
|
"context",
|
|
),
|
|
),
|
|
space_before: Space(
|
|
"",
|
|
),
|
|
separator: DoubleColon,
|
|
after: Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: Some(
|
|
(
|
|
Text(
|
|
"TyCtxt",
|
|
),
|
|
Space(
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
delimiter: Angle,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Lifetime(
|
|
"_",
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
},
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Delimited(
|
|
Delimited {
|
|
prefix: None,
|
|
delimiter: Paren,
|
|
contents: Segments {
|
|
segments: [
|
|
Segment {
|
|
leading_space: Space(
|
|
"",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
"?1c,",
|
|
),
|
|
),
|
|
},
|
|
Segment {
|
|
leading_space: Space(
|
|
" ",
|
|
),
|
|
token: Atom(
|
|
Text(
|
|
"?2c",
|
|
),
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
},
|
|
},
|
|
),
|
|
},
|
|
],
|
|
trailing_space: Space(
|
|
"",
|
|
),
|
|
}
|
|
"#)
|
|
}
|
|
}
|