]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_parse_format/src/lib.rs
Rollup merge of #96658 - Folyd:es6, r=GuillaumeGomez
[rust.git] / compiler / rustc_parse_format / src / lib.rs
1 //! Macro support for format strings
2 //!
3 //! These structures are used when parsing format strings for the compiler.
4 //! Parsing does not happen at runtime: structures of `std::fmt::rt` are
5 //! generated instead.
6
7 #![doc(
8     html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/",
9     html_playground_url = "https://play.rust-lang.org/",
10     test(attr(deny(warnings)))
11 )]
12 // We want to be able to build this crate with a stable compiler, so no
13 // `#![feature]` attributes should be added.
14
15 pub use Alignment::*;
16 pub use Count::*;
17 pub use Flag::*;
18 pub use Piece::*;
19 pub use Position::*;
20
21 use std::iter;
22 use std::str;
23 use std::string;
24
25 // Note: copied from rustc_span
26 /// Range inside of a `Span` used for diagnostics when we only have access to relative positions.
27 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
28 pub struct InnerSpan {
29     pub start: usize,
30     pub end: usize,
31 }
32
33 impl InnerSpan {
34     pub fn new(start: usize, end: usize) -> InnerSpan {
35         InnerSpan { start, end }
36     }
37 }
38
39 /// The type of format string that we are parsing.
40 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
41 pub enum ParseMode {
42     /// A normal format string as per `format_args!`.
43     Format,
44     /// An inline assembly template string for `asm!`.
45     InlineAsm,
46 }
47
48 #[derive(Copy, Clone)]
49 struct InnerOffset(usize);
50
51 impl InnerOffset {
52     fn to(self, end: InnerOffset) -> InnerSpan {
53         InnerSpan::new(self.0, end.0)
54     }
55 }
56
57 /// A piece is a portion of the format string which represents the next part
58 /// to emit. These are emitted as a stream by the `Parser` class.
59 #[derive(Copy, Clone, Debug, PartialEq)]
60 pub enum Piece<'a> {
61     /// A literal string which should directly be emitted
62     String(&'a str),
63     /// This describes that formatting should process the next argument (as
64     /// specified inside) for emission.
65     NextArgument(Argument<'a>),
66 }
67
68 /// Representation of an argument specification.
69 #[derive(Copy, Clone, Debug, PartialEq)]
70 pub struct Argument<'a> {
71     /// Where to find this argument
72     pub position: Position<'a>,
73     /// How to format the argument
74     pub format: FormatSpec<'a>,
75 }
76
77 /// Specification for the formatting of an argument in the format string.
78 #[derive(Copy, Clone, Debug, PartialEq)]
79 pub struct FormatSpec<'a> {
80     /// Optionally specified character to fill alignment with.
81     pub fill: Option<char>,
82     /// Optionally specified alignment.
83     pub align: Alignment,
84     /// Packed version of various flags provided.
85     pub flags: u32,
86     /// The integer precision to use.
87     pub precision: Count<'a>,
88     /// The span of the precision formatting flag (for diagnostics).
89     pub precision_span: Option<InnerSpan>,
90     /// The string width requested for the resulting format.
91     pub width: Count<'a>,
92     /// The span of the width formatting flag (for diagnostics).
93     pub width_span: Option<InnerSpan>,
94     /// The descriptor string representing the name of the format desired for
95     /// this argument, this can be empty or any number of characters, although
96     /// it is required to be one word.
97     pub ty: &'a str,
98     /// The span of the descriptor string (for diagnostics).
99     pub ty_span: Option<InnerSpan>,
100 }
101
102 /// Enum describing where an argument for a format can be located.
103 #[derive(Copy, Clone, Debug, PartialEq)]
104 pub enum Position<'a> {
105     /// The argument is implied to be located at an index
106     ArgumentImplicitlyIs(usize),
107     /// The argument is located at a specific index given in the format
108     ArgumentIs(usize),
109     /// The argument has a name.
110     ArgumentNamed(&'a str, InnerSpan),
111 }
112
113 impl Position<'_> {
114     pub fn index(&self) -> Option<usize> {
115         match self {
116             ArgumentIs(i) | ArgumentImplicitlyIs(i) => Some(*i),
117             _ => None,
118         }
119     }
120 }
121
122 /// Enum of alignments which are supported.
123 #[derive(Copy, Clone, Debug, PartialEq)]
124 pub enum Alignment {
125     /// The value will be aligned to the left.
126     AlignLeft,
127     /// The value will be aligned to the right.
128     AlignRight,
129     /// The value will be aligned in the center.
130     AlignCenter,
131     /// The value will take on a default alignment.
132     AlignUnknown,
133 }
134
135 /// Various flags which can be applied to format strings. The meaning of these
136 /// flags is defined by the formatters themselves.
137 #[derive(Copy, Clone, Debug, PartialEq)]
138 pub enum Flag {
139     /// A `+` will be used to denote positive numbers.
140     FlagSignPlus,
141     /// A `-` will be used to denote negative numbers. This is the default.
142     FlagSignMinus,
143     /// An alternate form will be used for the value. In the case of numbers,
144     /// this means that the number will be prefixed with the supplied string.
145     FlagAlternate,
146     /// For numbers, this means that the number will be padded with zeroes,
147     /// and the sign (`+` or `-`) will precede them.
148     FlagSignAwareZeroPad,
149     /// For Debug / `?`, format integers in lower-case hexadecimal.
150     FlagDebugLowerHex,
151     /// For Debug / `?`, format integers in upper-case hexadecimal.
152     FlagDebugUpperHex,
153 }
154
155 /// A count is used for the precision and width parameters of an integer, and
156 /// can reference either an argument or a literal integer.
157 #[derive(Copy, Clone, Debug, PartialEq)]
158 pub enum Count<'a> {
159     /// The count is specified explicitly.
160     CountIs(usize),
161     /// The count is specified by the argument with the given name.
162     CountIsName(&'a str, InnerSpan),
163     /// The count is specified by the argument at the given index.
164     CountIsParam(usize),
165     /// The count is implied and cannot be explicitly specified.
166     CountImplied,
167 }
168
169 pub struct ParseError {
170     pub description: string::String,
171     pub note: Option<string::String>,
172     pub label: string::String,
173     pub span: InnerSpan,
174     pub secondary_label: Option<(string::String, InnerSpan)>,
175 }
176
177 /// The parser structure for interpreting the input format string. This is
178 /// modeled as an iterator over `Piece` structures to form a stream of tokens
179 /// being output.
180 ///
181 /// This is a recursive-descent parser for the sake of simplicity, and if
182 /// necessary there's probably lots of room for improvement performance-wise.
183 pub struct Parser<'a> {
184     mode: ParseMode,
185     input: &'a str,
186     cur: iter::Peekable<str::CharIndices<'a>>,
187     /// Error messages accumulated during parsing
188     pub errors: Vec<ParseError>,
189     /// Current position of implicit positional argument pointer
190     pub curarg: usize,
191     /// `Some(raw count)` when the string is "raw", used to position spans correctly
192     style: Option<usize>,
193     /// Start and end byte offset of every successfully parsed argument
194     pub arg_places: Vec<InnerSpan>,
195     /// Characters that need to be shifted
196     skips: Vec<usize>,
197     /// Span of the last opening brace seen, used for error reporting
198     last_opening_brace: Option<InnerSpan>,
199     /// Whether the source string is comes from `println!` as opposed to `format!` or `print!`
200     append_newline: bool,
201     /// Whether this formatting string is a literal or it comes from a macro.
202     pub is_literal: bool,
203     /// Start position of the current line.
204     cur_line_start: usize,
205     /// Start and end byte offset of every line of the format string. Excludes
206     /// newline characters and leading whitespace.
207     pub line_spans: Vec<InnerSpan>,
208 }
209
210 impl<'a> Iterator for Parser<'a> {
211     type Item = Piece<'a>;
212
213     fn next(&mut self) -> Option<Piece<'a>> {
214         if let Some(&(pos, c)) = self.cur.peek() {
215             match c {
216                 '{' => {
217                     let curr_last_brace = self.last_opening_brace;
218                     let byte_pos = self.to_span_index(pos);
219                     self.last_opening_brace = Some(byte_pos.to(InnerOffset(byte_pos.0 + 1)));
220                     self.cur.next();
221                     if self.consume('{') {
222                         self.last_opening_brace = curr_last_brace;
223
224                         Some(String(self.string(pos + 1)))
225                     } else {
226                         let arg = self.argument();
227                         if let Some(rbrace_byte_idx) = self.must_consume('}') {
228                             let lbrace_inner_offset = self.to_span_index(pos);
229                             let rbrace_inner_offset = self.to_span_index(rbrace_byte_idx);
230                             if self.is_literal {
231                                 self.arg_places.push(
232                                     lbrace_inner_offset.to(InnerOffset(rbrace_inner_offset.0 + 1)),
233                                 );
234                             }
235                         }
236                         Some(NextArgument(arg))
237                     }
238                 }
239                 '}' => {
240                     self.cur.next();
241                     if self.consume('}') {
242                         Some(String(self.string(pos + 1)))
243                     } else {
244                         let err_pos = self.to_span_index(pos);
245                         self.err_with_note(
246                             "unmatched `}` found",
247                             "unmatched `}`",
248                             "if you intended to print `}`, you can escape it using `}}`",
249                             err_pos.to(err_pos),
250                         );
251                         None
252                     }
253                 }
254                 _ => Some(String(self.string(pos))),
255             }
256         } else {
257             if self.is_literal {
258                 let start = self.to_span_index(self.cur_line_start);
259                 let end = self.to_span_index(self.input.len());
260                 let span = start.to(end);
261                 if self.line_spans.last() != Some(&span) {
262                     self.line_spans.push(span);
263                 }
264             }
265             None
266         }
267     }
268 }
269
270 impl<'a> Parser<'a> {
271     /// Creates a new parser for the given format string
272     pub fn new(
273         s: &'a str,
274         style: Option<usize>,
275         snippet: Option<string::String>,
276         append_newline: bool,
277         mode: ParseMode,
278     ) -> Parser<'a> {
279         let (skips, is_literal) = find_skips_from_snippet(snippet, style);
280         Parser {
281             mode,
282             input: s,
283             cur: s.char_indices().peekable(),
284             errors: vec![],
285             curarg: 0,
286             style,
287             arg_places: vec![],
288             skips,
289             last_opening_brace: None,
290             append_newline,
291             is_literal,
292             cur_line_start: 0,
293             line_spans: vec![],
294         }
295     }
296
297     /// Notifies of an error. The message doesn't actually need to be of type
298     /// String, but I think it does when this eventually uses conditions so it
299     /// might as well start using it now.
300     fn err<S1: Into<string::String>, S2: Into<string::String>>(
301         &mut self,
302         description: S1,
303         label: S2,
304         span: InnerSpan,
305     ) {
306         self.errors.push(ParseError {
307             description: description.into(),
308             note: None,
309             label: label.into(),
310             span,
311             secondary_label: None,
312         });
313     }
314
315     /// Notifies of an error. The message doesn't actually need to be of type
316     /// String, but I think it does when this eventually uses conditions so it
317     /// might as well start using it now.
318     fn err_with_note<
319         S1: Into<string::String>,
320         S2: Into<string::String>,
321         S3: Into<string::String>,
322     >(
323         &mut self,
324         description: S1,
325         label: S2,
326         note: S3,
327         span: InnerSpan,
328     ) {
329         self.errors.push(ParseError {
330             description: description.into(),
331             note: Some(note.into()),
332             label: label.into(),
333             span,
334             secondary_label: None,
335         });
336     }
337
338     /// Optionally consumes the specified character. If the character is not at
339     /// the current position, then the current iterator isn't moved and `false` is
340     /// returned, otherwise the character is consumed and `true` is returned.
341     fn consume(&mut self, c: char) -> bool {
342         self.consume_pos(c).is_some()
343     }
344
345     /// Optionally consumes the specified character. If the character is not at
346     /// the current position, then the current iterator isn't moved and `None` is
347     /// returned, otherwise the character is consumed and the current position is
348     /// returned.
349     fn consume_pos(&mut self, c: char) -> Option<usize> {
350         if let Some(&(pos, maybe)) = self.cur.peek() {
351             if c == maybe {
352                 self.cur.next();
353                 return Some(pos);
354             }
355         }
356         None
357     }
358
359     fn to_span_index(&self, pos: usize) -> InnerOffset {
360         let mut pos = pos;
361         // This handles the raw string case, the raw argument is the number of #
362         // in r###"..."### (we need to add one because of the `r`).
363         let raw = self.style.map_or(0, |raw| raw + 1);
364         for skip in &self.skips {
365             if pos > *skip {
366                 pos += 1;
367             } else if pos == *skip && raw == 0 {
368                 pos += 1;
369             } else {
370                 break;
371             }
372         }
373         InnerOffset(raw + pos + 1)
374     }
375
376     /// Forces consumption of the specified character. If the character is not
377     /// found, an error is emitted.
378     fn must_consume(&mut self, c: char) -> Option<usize> {
379         self.ws();
380
381         if let Some(&(pos, maybe)) = self.cur.peek() {
382             if c == maybe {
383                 self.cur.next();
384                 Some(pos)
385             } else {
386                 let pos = self.to_span_index(pos);
387                 let description = format!("expected `'}}'`, found `{:?}`", maybe);
388                 let label = "expected `}`".to_owned();
389                 let (note, secondary_label) = if c == '}' {
390                     (
391                         Some(
392                             "if you intended to print `{`, you can escape it using `{{`".to_owned(),
393                         ),
394                         self.last_opening_brace
395                             .map(|sp| ("because of this opening brace".to_owned(), sp)),
396                     )
397                 } else {
398                     (None, None)
399                 };
400                 self.errors.push(ParseError {
401                     description,
402                     note,
403                     label,
404                     span: pos.to(pos),
405                     secondary_label,
406                 });
407                 None
408             }
409         } else {
410             let description = format!("expected `{:?}` but string was terminated", c);
411             // point at closing `"`
412             let pos = self.input.len() - if self.append_newline { 1 } else { 0 };
413             let pos = self.to_span_index(pos);
414             if c == '}' {
415                 let label = format!("expected `{:?}`", c);
416                 let (note, secondary_label) = if c == '}' {
417                     (
418                         Some(
419                             "if you intended to print `{`, you can escape it using `{{`".to_owned(),
420                         ),
421                         self.last_opening_brace
422                             .map(|sp| ("because of this opening brace".to_owned(), sp)),
423                     )
424                 } else {
425                     (None, None)
426                 };
427                 self.errors.push(ParseError {
428                     description,
429                     note,
430                     label,
431                     span: pos.to(pos),
432                     secondary_label,
433                 });
434             } else {
435                 self.err(description, format!("expected `{:?}`", c), pos.to(pos));
436             }
437             None
438         }
439     }
440
441     /// Consumes all whitespace characters until the first non-whitespace character
442     fn ws(&mut self) {
443         while let Some(&(_, c)) = self.cur.peek() {
444             if c.is_whitespace() {
445                 self.cur.next();
446             } else {
447                 break;
448             }
449         }
450     }
451
452     /// Parses all of a string which is to be considered a "raw literal" in a
453     /// format string. This is everything outside of the braces.
454     fn string(&mut self, start: usize) -> &'a str {
455         // we may not consume the character, peek the iterator
456         while let Some(&(pos, c)) = self.cur.peek() {
457             match c {
458                 '{' | '}' => {
459                     return &self.input[start..pos];
460                 }
461                 '\n' if self.is_literal => {
462                     let start = self.to_span_index(self.cur_line_start);
463                     let end = self.to_span_index(pos);
464                     self.line_spans.push(start.to(end));
465                     self.cur_line_start = pos + 1;
466                     self.cur.next();
467                 }
468                 _ => {
469                     if self.is_literal && pos == self.cur_line_start && c.is_whitespace() {
470                         self.cur_line_start = pos + c.len_utf8();
471                     }
472                     self.cur.next();
473                 }
474             }
475         }
476         &self.input[start..self.input.len()]
477     }
478
479     /// Parses an `Argument` structure, or what's contained within braces inside the format string.
480     fn argument(&mut self) -> Argument<'a> {
481         let pos = self.position();
482         let format = match self.mode {
483             ParseMode::Format => self.format(),
484             ParseMode::InlineAsm => self.inline_asm(),
485         };
486
487         // Resolve position after parsing format spec.
488         let pos = match pos {
489             Some(position) => position,
490             None => {
491                 let i = self.curarg;
492                 self.curarg += 1;
493                 ArgumentImplicitlyIs(i)
494             }
495         };
496
497         Argument { position: pos, format }
498     }
499
500     /// Parses a positional argument for a format. This could either be an
501     /// integer index of an argument, a named argument, or a blank string.
502     /// Returns `Some(parsed_position)` if the position is not implicitly
503     /// consuming a macro argument, `None` if it's the case.
504     fn position(&mut self) -> Option<Position<'a>> {
505         if let Some(i) = self.integer() {
506             Some(ArgumentIs(i))
507         } else {
508             match self.cur.peek() {
509                 Some(&(start, c)) if rustc_lexer::is_id_start(c) => {
510                     let word = self.word();
511                     let end = start + word.len();
512                     let span = self.to_span_index(start).to(self.to_span_index(end));
513                     Some(ArgumentNamed(word, span))
514                 }
515
516                 // This is an `ArgumentNext`.
517                 // Record the fact and do the resolution after parsing the
518                 // format spec, to make things like `{:.*}` work.
519                 _ => None,
520             }
521         }
522     }
523
524     /// Parses a format specifier at the current position, returning all of the
525     /// relevant information in the `FormatSpec` struct.
526     fn format(&mut self) -> FormatSpec<'a> {
527         let mut spec = FormatSpec {
528             fill: None,
529             align: AlignUnknown,
530             flags: 0,
531             precision: CountImplied,
532             precision_span: None,
533             width: CountImplied,
534             width_span: None,
535             ty: &self.input[..0],
536             ty_span: None,
537         };
538         if !self.consume(':') {
539             return spec;
540         }
541
542         // fill character
543         if let Some(&(_, c)) = self.cur.peek() {
544             if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) {
545                 spec.fill = Some(c);
546                 self.cur.next();
547             }
548         }
549         // Alignment
550         if self.consume('<') {
551             spec.align = AlignLeft;
552         } else if self.consume('>') {
553             spec.align = AlignRight;
554         } else if self.consume('^') {
555             spec.align = AlignCenter;
556         }
557         // Sign flags
558         if self.consume('+') {
559             spec.flags |= 1 << (FlagSignPlus as u32);
560         } else if self.consume('-') {
561             spec.flags |= 1 << (FlagSignMinus as u32);
562         }
563         // Alternate marker
564         if self.consume('#') {
565             spec.flags |= 1 << (FlagAlternate as u32);
566         }
567         // Width and precision
568         let mut havewidth = false;
569
570         if self.consume('0') {
571             // small ambiguity with '0$' as a format string. In theory this is a
572             // '0' flag and then an ill-formatted format string with just a '$'
573             // and no count, but this is better if we instead interpret this as
574             // no '0' flag and '0$' as the width instead.
575             if self.consume('$') {
576                 spec.width = CountIsParam(0);
577                 havewidth = true;
578             } else {
579                 spec.flags |= 1 << (FlagSignAwareZeroPad as u32);
580             }
581         }
582         if !havewidth {
583             let width_span_start = if let Some((pos, _)) = self.cur.peek() { *pos } else { 0 };
584             let (w, sp) = self.count(width_span_start);
585             spec.width = w;
586             spec.width_span = sp;
587         }
588         if let Some(start) = self.consume_pos('.') {
589             if let Some(end) = self.consume_pos('*') {
590                 // Resolve `CountIsNextParam`.
591                 // We can do this immediately as `position` is resolved later.
592                 let i = self.curarg;
593                 self.curarg += 1;
594                 spec.precision = CountIsParam(i);
595                 spec.precision_span =
596                     Some(self.to_span_index(start).to(self.to_span_index(end + 1)));
597             } else {
598                 let (p, sp) = self.count(start);
599                 spec.precision = p;
600                 spec.precision_span = sp;
601             }
602         }
603         let ty_span_start = self.cur.peek().map(|(pos, _)| *pos);
604         // Optional radix followed by the actual format specifier
605         if self.consume('x') {
606             if self.consume('?') {
607                 spec.flags |= 1 << (FlagDebugLowerHex as u32);
608                 spec.ty = "?";
609             } else {
610                 spec.ty = "x";
611             }
612         } else if self.consume('X') {
613             if self.consume('?') {
614                 spec.flags |= 1 << (FlagDebugUpperHex as u32);
615                 spec.ty = "?";
616             } else {
617                 spec.ty = "X";
618             }
619         } else if self.consume('?') {
620             spec.ty = "?";
621         } else {
622             spec.ty = self.word();
623             let ty_span_end = self.cur.peek().map(|(pos, _)| *pos);
624             if !spec.ty.is_empty() {
625                 spec.ty_span = ty_span_start
626                     .and_then(|s| ty_span_end.map(|e| (s, e)))
627                     .map(|(start, end)| self.to_span_index(start).to(self.to_span_index(end)));
628             }
629         }
630         spec
631     }
632
633     /// Parses an inline assembly template modifier at the current position, returning the modifier
634     /// in the `ty` field of the `FormatSpec` struct.
635     fn inline_asm(&mut self) -> FormatSpec<'a> {
636         let mut spec = FormatSpec {
637             fill: None,
638             align: AlignUnknown,
639             flags: 0,
640             precision: CountImplied,
641             precision_span: None,
642             width: CountImplied,
643             width_span: None,
644             ty: &self.input[..0],
645             ty_span: None,
646         };
647         if !self.consume(':') {
648             return spec;
649         }
650
651         let ty_span_start = self.cur.peek().map(|(pos, _)| *pos);
652         spec.ty = self.word();
653         let ty_span_end = self.cur.peek().map(|(pos, _)| *pos);
654         if !spec.ty.is_empty() {
655             spec.ty_span = ty_span_start
656                 .and_then(|s| ty_span_end.map(|e| (s, e)))
657                 .map(|(start, end)| self.to_span_index(start).to(self.to_span_index(end)));
658         }
659
660         spec
661     }
662
663     /// Parses a `Count` parameter at the current position. This does not check
664     /// for 'CountIsNextParam' because that is only used in precision, not
665     /// width.
666     fn count(&mut self, start: usize) -> (Count<'a>, Option<InnerSpan>) {
667         if let Some(i) = self.integer() {
668             if let Some(end) = self.consume_pos('$') {
669                 let span = self.to_span_index(start).to(self.to_span_index(end + 1));
670                 (CountIsParam(i), Some(span))
671             } else {
672                 (CountIs(i), None)
673             }
674         } else {
675             let tmp = self.cur.clone();
676             let word = self.word();
677             if word.is_empty() {
678                 self.cur = tmp;
679                 (CountImplied, None)
680             } else if let Some(end) = self.consume_pos('$') {
681                 let span = self.to_span_index(start + 1).to(self.to_span_index(end));
682                 (CountIsName(word, span), None)
683             } else {
684                 self.cur = tmp;
685                 (CountImplied, None)
686             }
687         }
688     }
689
690     /// Parses a word starting at the current position. A word is the same as
691     /// Rust identifier, except that it can't start with `_` character.
692     fn word(&mut self) -> &'a str {
693         let start = match self.cur.peek() {
694             Some(&(pos, c)) if rustc_lexer::is_id_start(c) => {
695                 self.cur.next();
696                 pos
697             }
698             _ => {
699                 return "";
700             }
701         };
702         let mut end = None;
703         while let Some(&(pos, c)) = self.cur.peek() {
704             if rustc_lexer::is_id_continue(c) {
705                 self.cur.next();
706             } else {
707                 end = Some(pos);
708                 break;
709             }
710         }
711         let end = end.unwrap_or(self.input.len());
712         let word = &self.input[start..end];
713         if word == "_" {
714             self.err_with_note(
715                 "invalid argument name `_`",
716                 "invalid argument name",
717                 "argument name cannot be a single underscore",
718                 self.to_span_index(start).to(self.to_span_index(end)),
719             );
720         }
721         word
722     }
723
724     /// Optionally parses an integer at the current position. This doesn't deal
725     /// with overflow at all, it's just accumulating digits.
726     fn integer(&mut self) -> Option<usize> {
727         let mut cur = 0;
728         let mut found = false;
729         while let Some(&(_, c)) = self.cur.peek() {
730             if let Some(i) = c.to_digit(10) {
731                 cur = cur * 10 + i as usize;
732                 found = true;
733                 self.cur.next();
734             } else {
735                 break;
736             }
737         }
738         if found { Some(cur) } else { None }
739     }
740 }
741
742 /// Finds the indices of all characters that have been processed and differ between the actual
743 /// written code (code snippet) and the `InternedString` that gets processed in the `Parser`
744 /// in order to properly synthesise the intra-string `Span`s for error diagnostics.
745 fn find_skips_from_snippet(
746     snippet: Option<string::String>,
747     str_style: Option<usize>,
748 ) -> (Vec<usize>, bool) {
749     let snippet = match snippet {
750         Some(ref s) if s.starts_with('"') || s.starts_with("r\"") || s.starts_with("r#") => s,
751         _ => return (vec![], false),
752     };
753
754     fn find_skips(snippet: &str, is_raw: bool) -> Vec<usize> {
755         let mut s = snippet.char_indices().peekable();
756         let mut skips = vec![];
757         while let Some((pos, c)) = s.next() {
758             match (c, s.peek()) {
759                 // skip whitespace and empty lines ending in '\\'
760                 ('\\', Some((next_pos, '\n'))) if !is_raw => {
761                     skips.push(pos);
762                     skips.push(*next_pos);
763                     let _ = s.next();
764
765                     while let Some((pos, c)) = s.peek() {
766                         if matches!(c, ' ' | '\n' | '\t') {
767                             skips.push(*pos);
768                             let _ = s.next();
769                         } else {
770                             break;
771                         }
772                     }
773                 }
774                 ('\\', Some((next_pos, 'n' | 't' | 'r' | '0' | '\\' | '\'' | '\"'))) => {
775                     skips.push(*next_pos);
776                     let _ = s.next();
777                 }
778                 ('\\', Some((_, 'x'))) if !is_raw => {
779                     for _ in 0..3 {
780                         // consume `\xAB` literal
781                         if let Some((pos, _)) = s.next() {
782                             skips.push(pos);
783                         } else {
784                             break;
785                         }
786                     }
787                 }
788                 ('\\', Some((_, 'u'))) if !is_raw => {
789                     if let Some((pos, _)) = s.next() {
790                         skips.push(pos);
791                     }
792                     if let Some((next_pos, next_c)) = s.next() {
793                         if next_c == '{' {
794                             skips.push(next_pos);
795                             let mut i = 0; // consume up to 6 hexanumeric chars + closing `}`
796                             while let (Some((next_pos, c)), true) = (s.next(), i < 7) {
797                                 if c.is_digit(16) {
798                                     skips.push(next_pos);
799                                 } else if c == '}' {
800                                     skips.push(next_pos);
801                                     break;
802                                 } else {
803                                     break;
804                                 }
805                                 i += 1;
806                             }
807                         } else if next_c.is_digit(16) {
808                             skips.push(next_pos);
809                             // We suggest adding `{` and `}` when appropriate, accept it here as if
810                             // it were correct
811                             let mut i = 0; // consume up to 6 hexanumeric chars
812                             while let (Some((next_pos, c)), _) = (s.next(), i < 6) {
813                                 if c.is_digit(16) {
814                                     skips.push(next_pos);
815                                 } else {
816                                     break;
817                                 }
818                                 i += 1;
819                             }
820                         }
821                     }
822                 }
823                 _ => {}
824             }
825         }
826         skips
827     }
828
829     let r_start = str_style.map_or(0, |r| r + 1);
830     let r_end = str_style.unwrap_or(0);
831     let s = &snippet[r_start + 1..snippet.len() - r_end - 1];
832     (find_skips(s, str_style.is_some()), true)
833 }
834
835 #[cfg(test)]
836 mod tests;