1 //! There are many AstNodes, but only a few tokens, so we hand-write them here.
3 use std::convert::{TryFrom, TryInto};
6 ast::{AstToken, Comment, RawString, String, Whitespace},
11 pub fn kind(&self) -> CommentKind {
12 kind_by_prefix(self.text())
15 pub fn prefix(&self) -> &'static str {
16 for (prefix, k) in COMMENT_PREFIX_TO_KIND.iter() {
17 if *k == self.kind() && self.text().starts_with(prefix) {
25 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
26 pub struct CommentKind {
27 pub shape: CommentShape,
28 pub doc: Option<CommentPlacement>,
31 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
32 pub enum CommentShape {
38 pub fn is_line(self) -> bool {
39 self == CommentShape::Line
42 pub fn is_block(self) -> bool {
43 self == CommentShape::Block
47 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
48 pub enum CommentPlacement {
53 const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = {
54 use {CommentPlacement::*, CommentShape::*};
56 ("////", CommentKind { shape: Line, doc: None }),
57 ("///", CommentKind { shape: Line, doc: Some(Outer) }),
58 ("//!", CommentKind { shape: Line, doc: Some(Inner) }),
59 ("/**", CommentKind { shape: Block, doc: Some(Outer) }),
60 ("/*!", CommentKind { shape: Block, doc: Some(Inner) }),
61 ("//", CommentKind { shape: Line, doc: None }),
62 ("/*", CommentKind { shape: Block, doc: None }),
66 fn kind_by_prefix(text: &str) -> CommentKind {
68 return CommentKind { shape: CommentShape::Block, doc: None };
70 for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() {
71 if text.starts_with(prefix) {
75 panic!("bad comment text: {:?}", text)
79 pub fn spans_multiple_lines(&self) -> bool {
80 let text = self.text();
81 text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
85 pub struct QuoteOffsets {
86 pub quotes: [TextRange; 2],
87 pub contents: TextRange,
91 fn new(literal: &str) -> Option<QuoteOffsets> {
92 let left_quote = literal.find('"')?;
93 let right_quote = literal.rfind('"')?;
94 if left_quote == right_quote {
95 // `literal` only contains one quote
99 let start = TextSize::from(0);
100 let left_quote = TextSize::try_from(left_quote).unwrap() + TextSize::of('"');
101 let right_quote = TextSize::try_from(right_quote).unwrap();
102 let end = TextSize::of(literal);
104 let res = QuoteOffsets {
105 quotes: [TextRange::new(start, left_quote), TextRange::new(right_quote, end)],
106 contents: TextRange::new(left_quote, right_quote),
112 pub trait HasQuotes: AstToken {
113 fn quote_offsets(&self) -> Option<QuoteOffsets> {
114 let text = self.text().as_str();
115 let offsets = QuoteOffsets::new(text)?;
116 let o = self.syntax().text_range().start();
117 let offsets = QuoteOffsets {
118 quotes: [offsets.quotes[0] + o, offsets.quotes[1] + o],
119 contents: offsets.contents + o,
123 fn open_quote_text_range(&self) -> Option<TextRange> {
124 self.quote_offsets().map(|it| it.quotes[0])
127 fn close_quote_text_range(&self) -> Option<TextRange> {
128 self.quote_offsets().map(|it| it.quotes[1])
131 fn text_range_between_quotes(&self) -> Option<TextRange> {
132 self.quote_offsets().map(|it| it.contents)
136 impl HasQuotes for String {}
137 impl HasQuotes for RawString {}
139 pub trait HasStringValue: HasQuotes {
140 fn value(&self) -> Option<std::string::String>;
143 impl HasStringValue for String {
144 fn value(&self) -> Option<std::string::String> {
145 let text = self.text().as_str();
146 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
148 let mut buf = std::string::String::with_capacity(text.len());
149 let mut has_error = false;
150 rustc_lexer::unescape::unescape_str(text, &mut |_, unescaped_char| match unescaped_char {
151 Ok(c) => buf.push(c),
152 Err(_) => has_error = true,
162 impl HasStringValue for RawString {
163 fn value(&self) -> Option<std::string::String> {
164 let text = self.text().as_str();
165 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
166 Some(text.to_string())
171 pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
172 let contents_range = self.text_range_between_quotes()?;
173 assert!(TextRange::up_to(contents_range.len()).contains_range(range));
174 Some(range + contents_range.start())
179 pub enum FormatSpecifier {
196 pub trait HasFormatSpecifier: AstToken {
199 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>;
201 fn lex_format_specifier<F>(&self, mut callback: F)
203 F: FnMut(TextRange, FormatSpecifier),
205 let char_ranges = if let Some(char_ranges) = self.char_ranges() {
210 let mut chars = char_ranges.iter().peekable();
212 while let Some((range, first_char)) = chars.next() {
215 // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
216 if let Some((_, Ok('{'))) = chars.peek() {
217 // Escaped format specifier, `{{`
222 callback(*range, FormatSpecifier::Open);
224 // check for integer/identifier
227 .and_then(|next| next.1.as_ref().ok())
233 read_integer(&mut chars, &mut callback);
235 c if c == '_' || c.is_alphabetic() => {
237 read_identifier(&mut chars, &mut callback);
242 if let Some((_, Ok(':'))) = chars.peek() {
243 skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback);
245 // check for fill/align
246 let mut cloned = chars.clone().take(2);
249 .and_then(|next| next.1.as_ref().ok())
251 .unwrap_or_default();
254 .and_then(|next| next.1.as_ref().ok())
256 .unwrap_or_default();
259 // alignment specifier, first char specifies fillment
262 FormatSpecifier::Fill,
267 FormatSpecifier::Align,
275 FormatSpecifier::Align,
286 .and_then(|next| next.1.as_ref().ok())
293 FormatSpecifier::Sign,
301 if let Some((_, Ok('#'))) = chars.peek() {
304 FormatSpecifier::NumberSign,
310 let mut cloned = chars.clone().take(2);
311 let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
312 let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
314 if first == Some('0') && second != Some('$') {
315 skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback);
321 .and_then(|next| next.1.as_ref().ok())
326 read_integer(&mut chars, &mut callback);
327 if let Some((_, Ok('$'))) = chars.peek() {
330 FormatSpecifier::DollarSign,
335 c if c == '_' || c.is_alphabetic() => {
336 read_identifier(&mut chars, &mut callback);
337 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
344 FormatSpecifier::DollarSign,
352 if let Some((_, Ok('.'))) = chars.peek() {
353 skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback);
357 .and_then(|next| next.1.as_ref().ok())
364 FormatSpecifier::Asterisk,
369 read_integer(&mut chars, &mut callback);
370 if let Some((_, Ok('$'))) = chars.peek() {
373 FormatSpecifier::DollarSign,
378 c if c == '_' || c.is_alphabetic() => {
379 read_identifier(&mut chars, &mut callback);
380 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
387 FormatSpecifier::DollarSign,
400 .and_then(|next| next.1.as_ref().ok())
407 FormatSpecifier::QuestionMark,
411 c if c == '_' || c.is_alphabetic() => {
412 read_identifier(&mut chars, &mut callback);
418 let mut cloned = chars.clone().take(2);
419 let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
420 let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
421 if first != Some('}') {
424 if second == Some('}') {
425 // Escaped format end specifier, `}}`
428 skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
431 while let Some((_, Ok(next_char))) = chars.peek() {
442 fn skip_char_and_emit<'a, I, F>(
443 chars: &mut std::iter::Peekable<I>,
444 emit: FormatSpecifier,
447 I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
448 F: FnMut(TextRange, FormatSpecifier),
450 let (range, _) = chars.next().unwrap();
451 callback(*range, emit);
454 fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
456 I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
457 F: FnMut(TextRange, FormatSpecifier),
459 let (mut range, c) = chars.next().unwrap();
460 assert!(c.as_ref().unwrap().is_ascii_digit());
461 while let Some((r, Ok(next_char))) = chars.peek() {
462 if next_char.is_ascii_digit() {
464 range = range.cover(*r);
469 callback(range, FormatSpecifier::Integer);
472 fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
474 I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
475 F: FnMut(TextRange, FormatSpecifier),
477 let (mut range, c) = chars.next().unwrap();
478 assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_');
479 while let Some((r, Ok(next_char))) = chars.peek() {
480 if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() {
482 range = range.cover(*r);
487 callback(range, FormatSpecifier::Identifier);
492 impl HasFormatSpecifier for String {
495 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
496 let text = self.text().as_str();
497 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
498 let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
500 let mut res = Vec::with_capacity(text.len());
501 rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| {
503 TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap())
513 impl HasFormatSpecifier for RawString {
516 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
517 let text = self.text().as_str();
518 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
519 let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
521 let mut res = Vec::with_capacity(text.len());
522 for (idx, c) in text.char_indices() {
523 res.push((TextRange::at(idx.try_into().unwrap(), TextSize::of(c)) + offset, Ok(c)));