use super::ast::*; use std::borrow::Cow; /// Text categories, based on the parsing. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum SpanKind { /// Parentheses e.g. /// /// Stores the delimiter depth, for e.g. rainbow delimiters. Delimiter(usize), /// Separators like `=`, ':' and ',' Separator, /// Numbers Number, /// Known literals, like `true`, `false`, `None`, `Ok`, `Err` Literal, /// Lifetimes (`'foo`) Lifetime, /// Strings String, /// Paths Path, /// Spaces (returns original number of spaces) Space(usize), /// Constructor: the prefix of a delimited block. /// i.e. `Some` in `Some(3)` Constructor, /// String prefix, suffix, hashtags, etc. /// Also number suffix Surroundings, /// Any other text (the default) Text, } /// A `Span` is a piece of categorized text, based on the parsing done by /// [`parse_input`](crate::parse_input). #[derive(Clone, PartialEq, Eq, Debug)] pub struct Span<'a> { /// The segment of text. pub text: Cow<'a, str>, /// Its category. pub kind: SpanKind, } /// Configuration options for [`into_spans`] #[derive(Default)] pub struct Config { /// Turn sequences of more than 1 space into exactly 1 space. pub collapse_space: bool, /// Pretty print: wrap at braces etc to the given width pub pretty_print: Option, } pub trait IntoSpans<'a>: private::IntoSpansImpl<'a> {} /// Turn an ast node into [`Span`]s. pub fn into_spans<'a>(ast: impl IntoSpans<'a>, config: Config) -> Vec> { let mut cx = private::Context { config, res: Vec::new(), depth: 0, ignore_next_space: false, }; if let Some(width) = cx.config.pretty_print { let res = ast.into_pretty_spans(&mut cx); let _ = res.render_vec(width, &mut cx.res); } else { ast.into_spans(&mut cx) } cx.res } mod private { use logparse_pretty_print::{PrettyBuilder, PrettyTree, Text}; use super::*; pub struct Context<'a> { pub config: Config, pub res: Vec>, pub depth: usize, pub ignore_next_space: bool, } impl<'a> Context<'a> { fn push(&mut self, text: impl Into>, kind: SpanKind) { self.res.push(Span { text: text.into(), kind, }) } } impl<'a> Text<'a> for Span<'a> { fn from_static_str(s: &'static str) -> Self { Span { text: Cow::Borrowed(s), kind: SpanKind::Text, } } fn as_str(&self) -> Cow<'_, str> { self.text.clone() } } pub trait IntoSpansImpl<'a> { fn into_spans(self, cx: &mut Context<'a>); fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>>; } impl<'a, T> IntoSpans<'a> for T where T: IntoSpansImpl<'a> {} impl<'a> IntoSpansImpl<'a> for Separator { fn into_spans(self, cx: &mut Context<'a>) { match self { Separator::Eq => cx.push("=", SpanKind::Separator), Separator::Colon => cx.push(":", SpanKind::Separator), Separator::DoubleColon => cx.push("::", SpanKind::Separator), } } fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { match self { Separator::Eq => PrettyTree::text(Span { text: Cow::Borrowed("="), kind: SpanKind::Separator, }), Separator::Colon => PrettyTree::text(Span { text: Cow::Borrowed(":"), kind: SpanKind::Separator, }), Separator::DoubleColon => PrettyTree::text(Span { text: Cow::Borrowed("::"), kind: SpanKind::Separator, }), } } } impl<'a> IntoSpansImpl<'a> for QuoteType { fn into_spans(self, cx: &mut Context<'a>) { match self { QuoteType::Single => cx.push("'", SpanKind::Surroundings), QuoteType::Double => cx.push("\"", SpanKind::Surroundings), QuoteType::Backtick => cx.push("`", SpanKind::Surroundings), } } fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { match self { QuoteType::Single => PrettyTree::text(Span { text: Cow::Borrowed("'"), kind: SpanKind::Surroundings, }), QuoteType::Double => PrettyTree::text(Span { text: Cow::Borrowed("\""), kind: SpanKind::Surroundings, }), QuoteType::Backtick => PrettyTree::text(Span { text: Cow::Borrowed("`"), kind: SpanKind::Surroundings, }), } } } impl<'a> IntoSpansImpl<'a> for AnyString<'a> { fn into_spans(self, cx: &mut Context<'a>) { let Self { prefix, ty, contents, num_hashtags, suffix, } = self; cx.push(prefix, SpanKind::Surroundings); for _ in 0..num_hashtags { cx.push("#", SpanKind::Surroundings) } ty.into_spans(cx); cx.push(contents, SpanKind::String); ty.into_spans(cx); for _ in 0..num_hashtags { cx.push("#", SpanKind::Surroundings) } cx.push(suffix, SpanKind::Surroundings); } fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { let Self { prefix, ty, contents, num_hashtags, suffix, } = self; let hashtags = "#".repeat(num_hashtags); PrettyTree::text(Span { text: prefix, kind: SpanKind::Surroundings, }) .append(PrettyTree::text(Span { text: Cow::Owned(hashtags.clone()), kind: SpanKind::Surroundings, })) .group() .append(ty.into_pretty_spans(cx)) .group() .append(PrettyTree::text(Span { text: contents, kind: SpanKind::String, })) .group() .append(ty.into_pretty_spans(cx)) .group() .append(PrettyTree::text(Span { text: suffix, kind: SpanKind::Surroundings, })) .group() } } impl<'a> IntoSpansImpl<'a> for Path<'a> { fn into_spans(self, cx: &mut Context<'a>) { cx.push(self.to_string(), SpanKind::Path) } fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { PrettyTree::text(Span { text: Cow::Owned(self.to_string()), kind: SpanKind::Path, }) } } impl<'a> IntoSpansImpl<'a> for Number<'a> { fn into_spans(self, cx: &mut Context<'a>) { cx.push(self.number, SpanKind::Number); if let Some(suffix) = self.suffix_without_underscore { cx.push("_", SpanKind::Surroundings); cx.push(suffix, SpanKind::Surroundings); } } fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { let main = PrettyTree::text(Span { text: self.number, kind: SpanKind::Number, }); let suffix = if let Some(suffix) = self.suffix_without_underscore { PrettyTree::text(Span { text: Cow::Borrowed("_"), kind: SpanKind::Surroundings, }) .append(PrettyTree::text(Span { text: suffix, kind: SpanKind::Surroundings, })) .group() } else { PrettyTree::Nil }; main.append(suffix).group() } } impl<'a> IntoSpansImpl<'a> for Atom<'a> { fn into_spans(self, cx: &mut Context<'a>) { match self { Atom::Text(text) => cx.push(text, SpanKind::Text), } } fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { match self { Atom::Text(text) => PrettyTree::text(Span { text, kind: SpanKind::Text, }), } } } impl<'a> IntoSpansImpl<'a> for Space<'a> { fn into_spans(self, cx: &mut Context<'a>) { match self.0.len() { 0 => {} 1 => cx.push(self.0, SpanKind::Space(1)), n if cx.config.collapse_space => cx.push(" ", SpanKind::Space(n)), n => cx.push(self.0, SpanKind::Space(n)), } } fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { if cx.ignore_next_space { cx.ignore_next_space = false; return PrettyTree::Nil; } match self.0.len() { 0 => PrettyTree::Nil, 1 => PrettyTree::line_or_space(), n if cx.config.collapse_space => PrettyTree::line_or_space(), n => PrettyTree::text(Span { text: self.0, kind: SpanKind::Space(n), }), } } } impl<'a> IntoSpansImpl<'a> for Token<'a> { fn into_spans(self, cx: &mut Context<'a>) { match self { Token::True => cx.push("true", SpanKind::Literal), Token::False => cx.push("false", SpanKind::Literal), Token::None => cx.push("None", SpanKind::Literal), Token::Path(path) => path.into_spans(cx), Token::String(string) => string.into_spans(cx), Token::Number(number) => number.into_spans(cx), Token::Separated { before, space_before, separator, after, } => { before.into_spans(cx); space_before.into_spans(cx); separator.into_spans(cx); after.into_spans(cx); } Token::Delimited(delimited) => { delimited.into_spans(cx); } Token::Atom(atom) => { atom.into_spans(cx); } Token::Lifetime(lifetime) => { cx.push("'", SpanKind::Surroundings); cx.push(lifetime, SpanKind::Lifetime); } } } fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { match self { Token::True => PrettyTree::text(Span { text: Cow::Borrowed("true"), kind: SpanKind::Literal, }), Token::False => PrettyTree::text(Span { text: Cow::Borrowed("true"), kind: SpanKind::Literal, }), Token::None => PrettyTree::text(Span { text: Cow::Borrowed("None"), kind: SpanKind::Literal, }), Token::Lifetime(lifetime) => PrettyTree::Text(Span { text: Cow::Borrowed("'"), kind: SpanKind::Surroundings, }) .append(PrettyTree::text(Span { text: lifetime, kind: SpanKind::Lifetime, })) .group(), Token::Path(path) => path.into_pretty_spans(cx), Token::String(string) => string.into_pretty_spans(cx), Token::Number(number) => number.into_pretty_spans(cx), Token::Separated { before, space_before, separator, after, } => before .into_pretty_spans(cx) .append(space_before.into_pretty_spans(cx)) .group() .append(separator.into_pretty_spans(cx)) .group() .append(after.into_pretty_spans(cx)) .group(), Token::Delimited(delimited) => delimited.into_pretty_spans(cx), Token::Atom(atom) => atom.into_pretty_spans(cx), } } } impl<'a> IntoSpansImpl<'a> for Segment<'a> { fn into_spans(self, cx: &mut Context<'a>) { let Self { leading_space, token, } = self; leading_space.into_spans(cx); token.into_spans(cx); } fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { let Self { leading_space, token, } = self; leading_space .into_pretty_spans(cx) .append(token.into_pretty_spans(cx)) .group() } } impl<'a> IntoSpansImpl<'a> for Segments<'a> { fn into_spans(self, cx: &mut Context<'a>) { let Self { segments, trailing_space, } = self; for segment in segments { segment.into_spans(cx); } trailing_space.into_spans(cx); } fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { let ignore_start = cx.ignore_next_space; let Self { segments, trailing_space, } = self; let mut res = PrettyTree::Nil; for segment in segments { res = res.append(segment.into_pretty_spans(cx)).group(); } // if we ignore the first space, also ignore the last one. if ignore_start { cx.ignore_next_space = true; } res.append(trailing_space.into_pretty_spans(cx)).group() } } fn prefix_kind(delimiter: Delimiter, space: &Space<'_>) -> SpanKind { match delimiter { Delimiter::Brace => SpanKind::Constructor, Delimiter::Paren if space.0.is_empty() => SpanKind::Constructor, Delimiter::Paren => SpanKind::Text, Delimiter::Bracket => SpanKind::Text, Delimiter::Angle if space.0.is_empty() => SpanKind::Constructor, Delimiter::Angle => SpanKind::Text, } } impl<'a> IntoSpansImpl<'a> for Delimited<'a> { fn into_spans(self, cx: &mut Context<'a>) { let Self { prefix, delimiter, contents, } = self; match prefix { Some((Atom::Text(text), space)) => { cx.push(text, prefix_kind(delimiter, &space)); space.into_spans(cx); } None => {} } match delimiter { Delimiter::Paren => cx.push("(", SpanKind::Delimiter(cx.depth)), Delimiter::Bracket => cx.push("[", SpanKind::Delimiter(cx.depth)), Delimiter::Brace => cx.push("{", SpanKind::Delimiter(cx.depth)), Delimiter::Angle => cx.push("<", SpanKind::Delimiter(cx.depth)), } cx.depth += 1; contents.into_spans(cx); cx.depth -= 1; match delimiter { Delimiter::Paren => cx.push(")", SpanKind::Delimiter(cx.depth)), Delimiter::Bracket => cx.push("]", SpanKind::Delimiter(cx.depth)), Delimiter::Brace => cx.push("}", SpanKind::Delimiter(cx.depth)), Delimiter::Angle => cx.push(">", SpanKind::Delimiter(cx.depth)), } } fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> { let Self { prefix, delimiter, contents, } = self; let group = contents .segments .iter() .filter(|i| !matches!(i.token, Token::Atom(..))) .count() <= 1; let prefix = match prefix { Some((Atom::Text(text), space)) => PrettyTree::text(Span { text, kind: prefix_kind(delimiter, &space), }) .append(space.into_pretty_spans(cx)) .group(), None => PrettyTree::Nil, }; let open = match delimiter { Delimiter::Paren => Span { text: Cow::Borrowed("("), kind: SpanKind::Delimiter(cx.depth), }, Delimiter::Bracket => Span { text: Cow::Borrowed("["), kind: SpanKind::Delimiter(cx.depth), }, Delimiter::Brace => Span { text: Cow::Borrowed("{"), kind: SpanKind::Delimiter(cx.depth), }, Delimiter::Angle => Span { text: Cow::Borrowed("<"), kind: SpanKind::Delimiter(cx.depth), }, }; let close = match delimiter { Delimiter::Paren => Span { text: Cow::Borrowed(")"), kind: SpanKind::Delimiter(cx.depth), }, Delimiter::Bracket => Span { text: Cow::Borrowed("]"), kind: SpanKind::Delimiter(cx.depth), }, Delimiter::Brace => Span { text: Cow::Borrowed("}"), kind: SpanKind::Delimiter(cx.depth), }, Delimiter::Angle => Span { text: Cow::Borrowed(">"), kind: SpanKind::Delimiter(cx.depth), }, }; cx.depth += 1; if !group { cx.ignore_next_space = true; } let contents = contents.into_pretty_spans(cx); cx.depth -= 1; if group { prefix .append(PrettyTree::text(open)) .group() .append(contents) .group() .append(PrettyTree::text(close)) } else { prefix .append(PrettyTree::text(open)) .group() .append(PrettyTree::Hardline) .group() .append(contents) .group() .nest(4) .group() .append(PrettyTree::Hardline) .group() .append(PrettyTree::text(close)) .group() } } } } #[cfg(test)] mod tests { use insta::{assert_debug_snapshot, assert_snapshot}; use super::SpanKind; use crate::{Config, into_spans, parse_input}; fn spans(input: &str) -> Vec<(String, SpanKind)> { let res = parse_input(input).unwrap(); into_spans( res, Config { collapse_space: true, ..Default::default() }, ) .into_iter() .map(|i| (i.text.into_owned(), i.kind)) .collect() } fn pretty_print(input: &str, n: usize) -> String { let res = parse_input(input).unwrap(); into_spans( res, Config { pretty_print: Some(n), ..Default::default() }, ) .into_iter() .map(|i| i.text.into_owned()) .collect::() } #[test] fn pretty_print_ex1() { assert_snapshot!(pretty_print( r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#, 60 ), @" def_id=DefId( 0:3 ~ unsized_coercion[10fa]::Trait ) "); assert_snapshot!(pretty_print( r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#, 30 ), @" def_id=DefId( 0:3 ~ unsized_coercion[10fa]::Trait ) "); assert_snapshot!(pretty_print( r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#, 10 ), @" def_id=DefId( 0:3 ~ unsized_coercion[10fa]::Trait ) "); } #[test] fn pretty_print_ex2() { assert_snapshot!(pretty_print( r#"stability: inspecting def_id=DefId(3:662 ~ alloc[ef11]::boxed::Box) span=tests/ui/impl-trait/unsized_coercion.rs:16:16: 16:30 (#0) of stability=Some(Stability { level: Stable { since: Version(RustcVersion { major: 1, minor: 0, patch: 0 }), allowed_through_unstable_modules: None }, feature: "rust1" })"#, 60 ), @r#" stability: inspecting def_id=DefId( 3:662 ~ alloc[ef11]::boxed::Box ) span=tests/ui/impl-trait/unsized_coercion.rs:16:16: 16:30 (#0) of stability=Some(Stability { level: Stable { since: Version(RustcVersion { major: 1, minor: 0, patch: 0 }), allowed_through_unstable_modules: None }, feature: "rust1" }) "#); } #[test] fn spans_ex1() { assert_debug_snapshot!(spans( r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"# ), @r#" [ ( "def_id", Text, ), ( "=", Separator, ), ( "DefId", Constructor, ), ( "(", Delimiter( 0, ), ), ( "0", Number, ), ( ":", Separator, ), ( "3", Number, ), ( " ", Space( 1, ), ), ( "~", Text, ), ( " ", Space( 1, ), ), ( "unsized_coercion", Text, ), ( "[", Delimiter( 1, ), ), ( "10fa", Text, ), ( "]", Delimiter( 1, ), ), ( "::", Separator, ), ( "Trait", Text, ), ( ")", Delimiter( 0, ), ), ] "#) } #[test] fn spans_ex2() { assert_debug_snapshot!(spans( r#"data=TypeNs("MetaSized") visible_parent=DefId(2:3984 ~ core[bcc4]::marker) actual_parent=Some(DefId(2:3984 ~ core[bcc4]::marker))"# ), @r#" [ ( "data", Text, ), ( "=", Separator, ), ( "TypeNs", Constructor, ), ( "(", Delimiter( 0, ), ), ( "", Surroundings, ), ( "\"", Surroundings, ), ( "MetaSized", String, ), ( "\"", Surroundings, ), ( "", Surroundings, ), ( ")", Delimiter( 0, ), ), ( " ", Space( 1, ), ), ( "visible_parent", Text, ), ( "=", Separator, ), ( "DefId", Constructor, ), ( "(", Delimiter( 0, ), ), ( "2", Number, ), ( ":", Separator, ), ( "3984", Number, ), ( " ", Space( 1, ), ), ( "~", Text, ), ( " ", Space( 1, ), ), ( "core", Text, ), ( "[", Delimiter( 1, ), ), ( "bcc4", Text, ), ( "]", Delimiter( 1, ), ), ( "::", Separator, ), ( "marker", Text, ), ( ")", Delimiter( 0, ), ), ( " ", Space( 1, ), ), ( "actual_parent", Text, ), ( "=", Separator, ), ( "Some", Constructor, ), ( "(", Delimiter( 0, ), ), ( "DefId", Constructor, ), ( "(", Delimiter( 1, ), ), ( "2", Number, ), ( ":", Separator, ), ( "3984", Number, ), ( " ", Space( 1, ), ), ( "~", Text, ), ( " ", Space( 1, ), ), ( "core", Text, ), ( "[", Delimiter( 2, ), ), ( "bcc4", Text, ), ( "]", Delimiter( 2, ), ), ( "::", Separator, ), ( "marker", Text, ), ( ")", Delimiter( 1, ), ), ( ")", Delimiter( 0, ), ), ] "#) } #[test] fn spans_ex3() { assert_debug_snapshot!(spans( r#"insert(DefId(0:4 ~ unsized_coercion[10fa]::{impl#0})): inserting TraitRef into specialization graph"# ), @r#" [ ( "insert", Constructor, ), ( "(", Delimiter( 0, ), ), ( "DefId", Constructor, ), ( "(", Delimiter( 1, ), ), ( "0", Number, ), ( ":", Separator, ), ( "4", Number, ), ( " ", Space( 1, ), ), ( "~", Text, ), ( " ", Space( 1, ), ), ( "unsized_coercion", Text, ), ( "[", Delimiter( 2, ), ), ( "10fa", Text, ), ( "]", Delimiter( 2, ), ), ( "::", Separator, ), ( "{", Delimiter( 2, ), ), ( "impl#0", Text, ), ( "}", Delimiter( 2, ), ), ( ")", Delimiter( 1, ), ), ( ")", Delimiter( 0, ), ), ( ":", Separator, ), ( " ", Space( 1, ), ), ( "inserting", Text, ), ( " ", Space( 1, ), ), ( "TraitRef", Text, ), ( " ", Space( 1, ), ), ( "<", Delimiter( 0, ), ), ( "u32", Text, ), ( " ", Space( 1, ), ), ( "as", Text, ), ( " ", Space( 1, ), ), ( "Trait", Text, ), ( ">", Delimiter( 0, ), ), ( " ", Space( 1, ), ), ( "into", Text, ), ( " ", Space( 1, ), ), ( "specialization", Text, ), ( " ", Space( 1, ), ), ( "graph", Text, ), ] "#) } #[test] fn spans_ex4() { assert_debug_snapshot!(spans( r#"inspecting def_id=DefId(3:662 ~ alloc[ef11]::boxed::Box) span=tests/ui/impl-trait/unsized_coercion.rs:12:15: 12:30 (#0)"# ), @r##" [ ( "inspecting", Text, ), ( " ", Space( 1, ), ), ( "def_id", Text, ), ( "=", Separator, ), ( "DefId", Constructor, ), ( "(", Delimiter( 0, ), ), ( "3", Number, ), ( ":", Separator, ), ( "662", Number, ), ( " ", Space( 1, ), ), ( "~", Text, ), ( " ", Space( 1, ), ), ( "alloc", Text, ), ( "[", Delimiter( 1, ), ), ( "ef11", Text, ), ( "]", Delimiter( 1, ), ), ( "::", Separator, ), ( "boxed", Text, ), ( "::", Separator, ), ( "Box", Text, ), ( ")", Delimiter( 0, ), ), ( " ", Space( 1, ), ), ( "span", Text, ), ( "=", Separator, ), ( "tests/ui/impl-trait/unsized_coercion.rs:12:15", Path, ), ( ":", Separator, ), ( " ", Space( 1, ), ), ( "12", Number, ), ( ":", Separator, ), ( "30", Number, ), ( " ", Space( 1, ), ), ( "(", Delimiter( 0, ), ), ( "#0", Text, ), ( ")", Delimiter( 0, ), ), ] "##) } }