diff options
Diffstat (limited to 'crates/syntax/src/ast/token_ext.rs')
-rw-r--r-- | crates/syntax/src/ast/token_ext.rs | 173 |
1 files changed, 118 insertions, 55 deletions
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index c5ef92733..e4e512f2e 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs | |||
@@ -8,11 +8,11 @@ use std::{ | |||
8 | use rustc_lexer::unescape::{unescape_literal, Mode}; | 8 | use rustc_lexer::unescape::{unescape_literal, Mode}; |
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{ |
11 | ast::{AstToken, Comment, RawString, String, Whitespace}, | 11 | ast::{self, AstToken}, |
12 | TextRange, TextSize, | 12 | TextRange, TextSize, |
13 | }; | 13 | }; |
14 | 14 | ||
15 | impl Comment { | 15 | impl ast::Comment { |
16 | pub fn kind(&self) -> CommentKind { | 16 | pub fn kind(&self) -> CommentKind { |
17 | kind_by_prefix(self.text()) | 17 | kind_by_prefix(self.text()) |
18 | } | 18 | } |
@@ -80,7 +80,7 @@ fn kind_by_prefix(text: &str) -> CommentKind { | |||
80 | panic!("bad comment text: {:?}", text) | 80 | panic!("bad comment text: {:?}", text) |
81 | } | 81 | } |
82 | 82 | ||
83 | impl Whitespace { | 83 | impl ast::Whitespace { |
84 | pub fn spans_multiple_lines(&self) -> bool { | 84 | pub fn spans_multiple_lines(&self) -> bool { |
85 | let text = self.text(); | 85 | let text = self.text(); |
86 | text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) | 86 | text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) |
@@ -114,43 +114,28 @@ impl QuoteOffsets { | |||
114 | } | 114 | } |
115 | } | 115 | } |
116 | 116 | ||
117 | pub trait HasQuotes: AstToken { | 117 | impl ast::String { |
118 | fn quote_offsets(&self) -> Option<QuoteOffsets> { | 118 | pub fn is_raw(&self) -> bool { |
119 | let text = self.text().as_str(); | 119 | self.text().starts_with('r') |
120 | let offsets = QuoteOffsets::new(text)?; | ||
121 | let o = self.syntax().text_range().start(); | ||
122 | let offsets = QuoteOffsets { | ||
123 | quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o), | ||
124 | contents: offsets.contents + o, | ||
125 | }; | ||
126 | Some(offsets) | ||
127 | } | ||
128 | fn open_quote_text_range(&self) -> Option<TextRange> { | ||
129 | self.quote_offsets().map(|it| it.quotes.0) | ||
130 | } | 120 | } |
131 | 121 | pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> { | |
132 | fn close_quote_text_range(&self) -> Option<TextRange> { | 122 | let contents_range = self.text_range_between_quotes()?; |
133 | self.quote_offsets().map(|it| it.quotes.1) | 123 | assert!(TextRange::up_to(contents_range.len()).contains_range(range)); |
124 | Some(range + contents_range.start()) | ||
134 | } | 125 | } |
135 | 126 | ||
136 | fn text_range_between_quotes(&self) -> Option<TextRange> { | 127 | pub fn value(&self) -> Option<Cow<'_, str>> { |
137 | self.quote_offsets().map(|it| it.contents) | 128 | if self.is_raw() { |
138 | } | 129 | let text = self.text().as_str(); |
139 | } | 130 | let text = |
140 | 131 | &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | |
141 | impl HasQuotes for String {} | 132 | return Some(Cow::Borrowed(text)); |
142 | impl HasQuotes for RawString {} | 133 | } |
143 | |||
144 | pub trait HasStringValue: HasQuotes { | ||
145 | fn value(&self) -> Option<Cow<'_, str>>; | ||
146 | } | ||
147 | 134 | ||
148 | impl HasStringValue for String { | ||
149 | fn value(&self) -> Option<Cow<'_, str>> { | ||
150 | let text = self.text().as_str(); | 135 | let text = self.text().as_str(); |
151 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | 136 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; |
152 | 137 | ||
153 | let mut buf = std::string::String::with_capacity(text.len()); | 138 | let mut buf = String::with_capacity(text.len()); |
154 | let mut has_error = false; | 139 | let mut has_error = false; |
155 | unescape_literal(text, Mode::Str, &mut |_, unescaped_char| match unescaped_char { | 140 | unescape_literal(text, Mode::Str, &mut |_, unescaped_char| match unescaped_char { |
156 | Ok(c) => buf.push(c), | 141 | Ok(c) => buf.push(c), |
@@ -164,21 +149,31 @@ impl HasStringValue for String { | |||
164 | let res = if buf == text { Cow::Borrowed(text) } else { Cow::Owned(buf) }; | 149 | let res = if buf == text { Cow::Borrowed(text) } else { Cow::Owned(buf) }; |
165 | Some(res) | 150 | Some(res) |
166 | } | 151 | } |
167 | } | ||
168 | 152 | ||
169 | impl HasStringValue for RawString { | 153 | pub fn quote_offsets(&self) -> Option<QuoteOffsets> { |
170 | fn value(&self) -> Option<Cow<'_, str>> { | ||
171 | let text = self.text().as_str(); | 154 | let text = self.text().as_str(); |
172 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | 155 | let offsets = QuoteOffsets::new(text)?; |
173 | Some(Cow::Borrowed(text)) | 156 | let o = self.syntax().text_range().start(); |
157 | let offsets = QuoteOffsets { | ||
158 | quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o), | ||
159 | contents: offsets.contents + o, | ||
160 | }; | ||
161 | Some(offsets) | ||
162 | } | ||
163 | pub fn text_range_between_quotes(&self) -> Option<TextRange> { | ||
164 | self.quote_offsets().map(|it| it.contents) | ||
165 | } | ||
166 | pub fn open_quote_text_range(&self) -> Option<TextRange> { | ||
167 | self.quote_offsets().map(|it| it.quotes.0) | ||
168 | } | ||
169 | pub fn close_quote_text_range(&self) -> Option<TextRange> { | ||
170 | self.quote_offsets().map(|it| it.quotes.1) | ||
174 | } | 171 | } |
175 | } | 172 | } |
176 | 173 | ||
177 | impl RawString { | 174 | impl ast::ByteString { |
178 | pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> { | 175 | pub fn is_raw(&self) -> bool { |
179 | let contents_range = self.text_range_between_quotes()?; | 176 | self.text().starts_with("br") |
180 | assert!(TextRange::up_to(contents_range.len()).contains_range(range)); | ||
181 | Some(range + contents_range.start()) | ||
182 | } | 177 | } |
183 | } | 178 | } |
184 | 179 | ||
@@ -500,7 +495,7 @@ pub trait HasFormatSpecifier: AstToken { | |||
500 | } | 495 | } |
501 | } | 496 | } |
502 | 497 | ||
503 | impl HasFormatSpecifier for String { | 498 | impl HasFormatSpecifier for ast::String { |
504 | fn char_ranges( | 499 | fn char_ranges( |
505 | &self, | 500 | &self, |
506 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | 501 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { |
@@ -521,18 +516,86 @@ impl HasFormatSpecifier for String { | |||
521 | } | 516 | } |
522 | } | 517 | } |
523 | 518 | ||
524 | impl HasFormatSpecifier for RawString { | 519 | impl ast::IntNumber { |
525 | fn char_ranges( | 520 | const SUFFIXES: &'static [&'static str] = &[ |
526 | &self, | 521 | "u8", "u16", "u32", "u64", "u128", "usize", // Unsigned. |
527 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | 522 | "i8", "i16", "i32", "i64", "i128", "isize", // Signed. |
528 | let text = self.text().as_str(); | 523 | ]; |
529 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | 524 | |
530 | let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); | 525 | pub fn radix(&self) -> Radix { |
526 | match self.text().get(..2).unwrap_or_default() { | ||
527 | "0b" => Radix::Binary, | ||
528 | "0o" => Radix::Octal, | ||
529 | "0x" => Radix::Hexadecimal, | ||
530 | _ => Radix::Decimal, | ||
531 | } | ||
532 | } | ||
531 | 533 | ||
532 | let mut res = Vec::with_capacity(text.len()); | 534 | pub fn value(&self) -> Option<u128> { |
533 | for (idx, c) in text.char_indices() { | 535 | let token = self.syntax(); |
534 | res.push((TextRange::at(idx.try_into().unwrap(), TextSize::of(c)) + offset, Ok(c))); | 536 | |
537 | let mut text = token.text().as_str(); | ||
538 | if let Some(suffix) = self.suffix() { | ||
539 | text = &text[..text.len() - suffix.len()] | ||
540 | } | ||
541 | |||
542 | let radix = self.radix(); | ||
543 | text = &text[radix.prefix_len()..]; | ||
544 | |||
545 | let buf; | ||
546 | if text.contains("_") { | ||
547 | buf = text.replace('_', ""); | ||
548 | text = buf.as_str(); | ||
549 | }; | ||
550 | |||
551 | let value = u128::from_str_radix(text, radix as u32).ok()?; | ||
552 | Some(value) | ||
553 | } | ||
554 | |||
555 | pub fn suffix(&self) -> Option<&str> { | ||
556 | let text = self.text(); | ||
557 | // FIXME: don't check a fixed set of suffixes, `1_0_1_l_o_l` is valid | ||
558 | // syntax, suffix is `l_o_l`. | ||
559 | ast::IntNumber::SUFFIXES.iter().chain(ast::FloatNumber::SUFFIXES.iter()).find_map( | ||
560 | |suffix| { | ||
561 | if text.ends_with(suffix) { | ||
562 | return Some(&text[text.len() - suffix.len()..]); | ||
563 | } | ||
564 | None | ||
565 | }, | ||
566 | ) | ||
567 | } | ||
568 | } | ||
569 | |||
570 | impl ast::FloatNumber { | ||
571 | const SUFFIXES: &'static [&'static str] = &["f32", "f64"]; | ||
572 | pub fn suffix(&self) -> Option<&str> { | ||
573 | let text = self.text(); | ||
574 | ast::FloatNumber::SUFFIXES.iter().find_map(|suffix| { | ||
575 | if text.ends_with(suffix) { | ||
576 | return Some(&text[text.len() - suffix.len()..]); | ||
577 | } | ||
578 | None | ||
579 | }) | ||
580 | } | ||
581 | } | ||
582 | |||
583 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | ||
584 | pub enum Radix { | ||
585 | Binary = 2, | ||
586 | Octal = 8, | ||
587 | Decimal = 10, | ||
588 | Hexadecimal = 16, | ||
589 | } | ||
590 | |||
591 | impl Radix { | ||
592 | pub const ALL: &'static [Radix] = | ||
593 | &[Radix::Binary, Radix::Octal, Radix::Decimal, Radix::Hexadecimal]; | ||
594 | |||
595 | const fn prefix_len(&self) -> usize { | ||
596 | match self { | ||
597 | Self::Decimal => 0, | ||
598 | _ => 2, | ||
535 | } | 599 | } |
536 | Some(res) | ||
537 | } | 600 | } |
538 | } | 601 | } |