mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-20 03:27:30 +00:00
954 lines
36 KiB
Rust
954 lines
36 KiB
Rust
//! Macro support for format strings
|
|
//!
|
|
//! These structures are used when parsing format strings for the compiler.
|
|
//! Parsing does not happen at runtime: structures of `std::fmt::rt` are
|
|
//! generated instead.
|
|
|
|
// tidy-alphabetical-start
|
|
// We want to be able to build this crate with a stable compiler,
|
|
// so no `#![feature]` attributes should be added.
|
|
#![deny(unstable_features)]
|
|
#![doc(
|
|
html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/",
|
|
html_playground_url = "https://play.rust-lang.org/",
|
|
test(attr(deny(warnings)))
|
|
)]
|
|
// tidy-alphabetical-end
|
|
|
|
use std::ops::Range;
|
|
|
|
pub use Alignment::*;
|
|
pub use Count::*;
|
|
pub use Position::*;
|
|
use rustc_literal_escaper::{Mode, unescape_unicode};
|
|
|
|
/// The type of format string that we are parsing.
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
pub enum ParseMode {
|
|
/// A normal format string as per `format_args!`.
|
|
Format,
|
|
/// An inline assembly template string for `asm!`.
|
|
InlineAsm,
|
|
}
|
|
|
|
/// A piece is a portion of the format string which represents the next part
|
|
/// to emit. These are emitted as a stream by the `Parser` class.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum Piece<'a> {
|
|
/// A literal string which should directly be emitted
|
|
Lit(&'a str),
|
|
/// This describes that formatting should process the next argument (as
|
|
/// specified inside) for emission.
|
|
NextArgument(Box<Argument<'a>>),
|
|
}
|
|
|
|
/// Representation of an argument specification.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct Argument<'a> {
|
|
/// Where to find this argument
|
|
pub position: Position<'a>,
|
|
/// The span of the position indicator. Includes any whitespace in implicit
|
|
/// positions (`{ }`).
|
|
pub position_span: Range<usize>,
|
|
/// How to format the argument
|
|
pub format: FormatSpec<'a>,
|
|
}
|
|
|
|
impl<'a> Argument<'a> {
|
|
pub fn is_identifier(&self) -> bool {
|
|
matches!(self.position, Position::ArgumentNamed(_))
|
|
&& matches!(
|
|
self.format,
|
|
FormatSpec {
|
|
fill: None,
|
|
fill_span: None,
|
|
align: AlignUnknown,
|
|
sign: None,
|
|
alternate: false,
|
|
zero_pad: false,
|
|
debug_hex: None,
|
|
precision: CountImplied,
|
|
precision_span: None,
|
|
width: CountImplied,
|
|
width_span: None,
|
|
ty: "",
|
|
ty_span: None,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Specification for the formatting of an argument in the format string.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct FormatSpec<'a> {
|
|
/// Optionally specified character to fill alignment with.
|
|
pub fill: Option<char>,
|
|
/// Span of the optionally specified fill character.
|
|
pub fill_span: Option<Range<usize>>,
|
|
/// Optionally specified alignment.
|
|
pub align: Alignment,
|
|
/// The `+` or `-` flag.
|
|
pub sign: Option<Sign>,
|
|
/// The `#` flag.
|
|
pub alternate: bool,
|
|
/// The `0` flag.
|
|
pub zero_pad: bool,
|
|
/// The `x` or `X` flag. (Only for `Debug`.)
|
|
pub debug_hex: Option<DebugHex>,
|
|
/// The integer precision to use.
|
|
pub precision: Count<'a>,
|
|
/// The span of the precision formatting flag (for diagnostics).
|
|
pub precision_span: Option<Range<usize>>,
|
|
/// The string width requested for the resulting format.
|
|
pub width: Count<'a>,
|
|
/// The span of the width formatting flag (for diagnostics).
|
|
pub width_span: Option<Range<usize>>,
|
|
/// The descriptor string representing the name of the format desired for
|
|
/// this argument, this can be empty or any number of characters, although
|
|
/// it is required to be one word.
|
|
pub ty: &'a str,
|
|
/// The span of the descriptor string (for diagnostics).
|
|
pub ty_span: Option<Range<usize>>,
|
|
}
|
|
|
|
/// Enum describing where an argument for a format can be located.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum Position<'a> {
|
|
/// The argument is implied to be located at an index
|
|
ArgumentImplicitlyIs(usize),
|
|
/// The argument is located at a specific index given in the format,
|
|
ArgumentIs(usize),
|
|
/// The argument has a name.
|
|
ArgumentNamed(&'a str),
|
|
}
|
|
|
|
impl Position<'_> {
|
|
pub fn index(&self) -> Option<usize> {
|
|
match self {
|
|
ArgumentIs(i, ..) | ArgumentImplicitlyIs(i) => Some(*i),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Enum of alignments which are supported.
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub enum Alignment {
|
|
/// The value will be aligned to the left.
|
|
AlignLeft,
|
|
/// The value will be aligned to the right.
|
|
AlignRight,
|
|
/// The value will be aligned in the center.
|
|
AlignCenter,
|
|
/// The value will take on a default alignment.
|
|
AlignUnknown,
|
|
}
|
|
|
|
/// Enum for the sign flags.
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub enum Sign {
|
|
/// The `+` flag.
|
|
Plus,
|
|
/// The `-` flag.
|
|
Minus,
|
|
}
|
|
|
|
/// Enum for the debug hex flags.
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub enum DebugHex {
|
|
/// The `x` flag in `{:x?}`.
|
|
Lower,
|
|
/// The `X` flag in `{:X?}`.
|
|
Upper,
|
|
}
|
|
|
|
/// A count is used for the precision and width parameters of an integer, and
|
|
/// can reference either an argument or a literal integer.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum Count<'a> {
|
|
/// The count is specified explicitly.
|
|
CountIs(u16),
|
|
/// The count is specified by the argument with the given name.
|
|
CountIsName(&'a str, Range<usize>),
|
|
/// The count is specified by the argument at the given index.
|
|
CountIsParam(usize),
|
|
/// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index.
|
|
CountIsStar(usize),
|
|
/// The count is implied and cannot be explicitly specified.
|
|
CountImplied,
|
|
}
|
|
|
|
pub struct ParseError {
|
|
pub description: String,
|
|
pub note: Option<String>,
|
|
pub label: String,
|
|
pub span: Range<usize>,
|
|
pub secondary_label: Option<(String, Range<usize>)>,
|
|
pub suggestion: Suggestion,
|
|
}
|
|
|
|
pub enum Suggestion {
|
|
None,
|
|
/// Replace inline argument with positional argument:
|
|
/// `format!("{foo.bar}")` -> `format!("{}", foo.bar)`
|
|
UsePositional,
|
|
/// Remove `r#` from identifier:
|
|
/// `format!("{r#foo}")` -> `format!("{foo}")`
|
|
RemoveRawIdent(Range<usize>),
|
|
/// Reorder format parameter:
|
|
/// `format!("{foo:?#}")` -> `format!("{foo:#?}")`
|
|
/// `format!("{foo:?x}")` -> `format!("{foo:x?}")`
|
|
/// `format!("{foo:?X}")` -> `format!("{foo:X?}")`
|
|
ReorderFormatParameter(Range<usize>, String),
|
|
}
|
|
|
|
/// The parser structure for interpreting the input format string. This is
|
|
/// modeled as an iterator over `Piece` structures to form a stream of tokens
|
|
/// being output.
|
|
///
|
|
/// This is a recursive-descent parser for the sake of simplicity, and if
|
|
/// necessary there's probably lots of room for improvement performance-wise.
|
|
pub struct Parser<'a> {
|
|
mode: ParseMode,
|
|
/// Input to be parsed
|
|
input: &'a str,
|
|
/// Tuples of the span in the code snippet (input as written before being unescaped), the pos in input, and the char in input
|
|
input_vec: Vec<(Range<usize>, usize, char)>,
|
|
/// Index into input_vec
|
|
input_vec_index: usize,
|
|
/// Error messages accumulated during parsing
|
|
pub errors: Vec<ParseError>,
|
|
/// Current position of implicit positional argument pointer
|
|
pub curarg: usize,
|
|
/// Start and end byte offset of every successfully parsed argument
|
|
pub arg_places: Vec<Range<usize>>,
|
|
/// Span of the last opening brace seen, used for error reporting
|
|
last_open_brace: Option<Range<usize>>,
|
|
/// Whether this formatting string was written directly in the source. This controls whether we
|
|
/// can use spans to refer into it and give better error messages.
|
|
/// N.B: This does _not_ control whether implicit argument captures can be used.
|
|
pub is_source_literal: bool,
|
|
/// Index to the end of the literal snippet
|
|
end_of_snippet: usize,
|
|
/// Start position of the current line.
|
|
cur_line_start: usize,
|
|
/// Start and end byte offset of every line of the format string. Excludes
|
|
/// newline characters and leading whitespace.
|
|
pub line_spans: Vec<Range<usize>>,
|
|
}
|
|
|
|
impl<'a> Iterator for Parser<'a> {
|
|
type Item = Piece<'a>;
|
|
|
|
fn next(&mut self) -> Option<Piece<'a>> {
|
|
if let Some(&(Range { start, end }, idx, ch)) = self.input_vec.get(self.input_vec_index) {
|
|
match ch {
|
|
'{' => {
|
|
self.input_vec_index += 1;
|
|
if let Some(&(_, i, '{')) = self.input_vec.get(self.input_vec_index) {
|
|
self.input_vec_index += 1;
|
|
// double open brace escape: "{{"
|
|
// next state after this is either end-of-input or seen-a-brace
|
|
Some(Piece::Lit(self.string(i)))
|
|
} else {
|
|
// single open brace
|
|
self.last_open_brace = Some(start..end);
|
|
let arg = self.argument();
|
|
if let Some(close_brace_range) = self.consume_closing_brace(&arg) {
|
|
if self.is_source_literal {
|
|
self.arg_places.push(start..close_brace_range.end);
|
|
}
|
|
} else if let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) {
|
|
match c {
|
|
'?' => self.suggest_format_debug(),
|
|
'<' | '^' | '>' => self.suggest_format_align(c),
|
|
_ => {
|
|
self.suggest_positional_arg_instead_of_captured_arg(arg.clone())
|
|
}
|
|
}
|
|
}
|
|
Some(Piece::NextArgument(Box::new(arg)))
|
|
}
|
|
}
|
|
'}' => {
|
|
self.input_vec_index += 1;
|
|
if let Some(&(_, i, '}')) = self.input_vec.get(self.input_vec_index) {
|
|
self.input_vec_index += 1;
|
|
// double close brace escape: "}}"
|
|
// next state after this is either end-of-input or start
|
|
Some(Piece::Lit(self.string(i)))
|
|
} else {
|
|
// error: single close brace without corresponding open brace
|
|
self.errors.push(ParseError {
|
|
description: "unmatched `}` found".into(),
|
|
note: Some(
|
|
"if you intended to print `}`, you can escape it using `}}`".into(),
|
|
),
|
|
label: "unmatched `}`".into(),
|
|
span: start..end,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::None,
|
|
});
|
|
None
|
|
}
|
|
}
|
|
_ => Some(Piece::Lit(self.string(idx))),
|
|
}
|
|
} else {
|
|
// end of input
|
|
if self.is_source_literal {
|
|
let span = self.cur_line_start..self.end_of_snippet;
|
|
if self.line_spans.last() != Some(&span) {
|
|
self.line_spans.push(span);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Parser<'a> {
|
|
/// Creates a new parser for the given unescaped input string and
|
|
/// optional code snippet (the input as written before being unescaped),
|
|
/// where `style` is `Some(nr_hashes)` when the snippet is a raw string with that many hashes.
|
|
/// If the input comes via `println` or `panic`, then it has a newline already appended,
|
|
/// which is reflected in the `appended_newline` parameter.
|
|
pub fn new(
|
|
input: &'a str,
|
|
style: Option<usize>,
|
|
snippet: Option<String>,
|
|
appended_newline: bool,
|
|
mode: ParseMode,
|
|
) -> Self {
|
|
let quote_offset = style.map_or(1, |nr_hashes| nr_hashes + 2);
|
|
|
|
let (is_source_literal, end_of_snippet, pre_input_vec) = if let Some(snippet) = snippet {
|
|
if let Some(nr_hashes) = style {
|
|
// snippet is a raw string, which starts with 'r', a number of hashes, and a quote
|
|
// and ends with a quote and the same number of hashes
|
|
(true, snippet.len() - nr_hashes - 1, vec![])
|
|
} else {
|
|
// snippet is not a raw string
|
|
if snippet.starts_with('"') {
|
|
// snippet looks like an ordinary string literal
|
|
// check whether it is the escaped version of input
|
|
let without_quotes = &snippet[1..snippet.len() - 1];
|
|
let (mut ok, mut vec) = (true, vec![]);
|
|
let mut chars = input.chars();
|
|
unescape_unicode(without_quotes, Mode::Str, &mut |range, res| match res {
|
|
Ok(ch) if ok && chars.next().is_some_and(|c| ch == c) => {
|
|
vec.push((range, ch));
|
|
}
|
|
_ => {
|
|
ok = false;
|
|
vec = vec![];
|
|
}
|
|
});
|
|
let end = vec.last().map(|(r, _)| r.end).unwrap_or(0);
|
|
if ok {
|
|
if appended_newline {
|
|
if chars.as_str() == "\n" {
|
|
vec.push((end..end + 1, '\n'));
|
|
(true, 1 + end, vec)
|
|
} else {
|
|
(false, snippet.len(), vec![])
|
|
}
|
|
} else if chars.as_str() == "" {
|
|
(true, 1 + end, vec)
|
|
} else {
|
|
(false, snippet.len(), vec![])
|
|
}
|
|
} else {
|
|
(false, snippet.len(), vec![])
|
|
}
|
|
} else {
|
|
// snippet is not a raw string and does not start with '"'
|
|
(false, snippet.len(), vec![])
|
|
}
|
|
}
|
|
} else {
|
|
// snippet is None
|
|
(false, input.len() - if appended_newline { 1 } else { 0 }, vec![])
|
|
};
|
|
|
|
let input_vec: Vec<(Range<usize>, usize, char)> = if pre_input_vec.is_empty() {
|
|
// Snippet is *not* input before unescaping, so spans pointing at it will be incorrect.
|
|
// This can happen with proc macros that respan generated literals.
|
|
input
|
|
.char_indices()
|
|
.map(|(idx, c)| {
|
|
let i = idx + quote_offset;
|
|
(i..i + c.len_utf8(), idx, c)
|
|
})
|
|
.collect()
|
|
} else {
|
|
// Snippet is input before unescaping
|
|
input
|
|
.char_indices()
|
|
.zip(pre_input_vec)
|
|
.map(|((i, c), (r, _))| (r.start + quote_offset..r.end + quote_offset, i, c))
|
|
.collect()
|
|
};
|
|
|
|
Parser {
|
|
mode,
|
|
input,
|
|
input_vec,
|
|
input_vec_index: 0,
|
|
errors: vec![],
|
|
curarg: 0,
|
|
arg_places: vec![],
|
|
last_open_brace: None,
|
|
is_source_literal,
|
|
end_of_snippet,
|
|
cur_line_start: quote_offset,
|
|
line_spans: vec![],
|
|
}
|
|
}
|
|
|
|
/// Optionally consumes the specified character. If the character is not at
|
|
/// the current position, then the current iterator isn't moved and `false` is
|
|
/// returned, otherwise the character is consumed and `true` is returned.
|
|
fn consume(&mut self, c: char) -> bool {
|
|
self.consume_pos(c).is_some()
|
|
}
|
|
|
|
/// Optionally consumes the specified character. If the character is not at
|
|
/// the current position, then the current iterator isn't moved and `None` is
|
|
/// returned, otherwise the character is consumed and the current position is
|
|
/// returned.
|
|
fn consume_pos(&mut self, ch: char) -> Option<(Range<usize>, usize)> {
|
|
if let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) {
|
|
if ch == *c {
|
|
self.input_vec_index += 1;
|
|
return Some((r.clone(), *i));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Forces consumption of the specified character. If the character is not
|
|
/// found, an error is emitted.
|
|
fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option<Range<usize>> {
|
|
self.ws();
|
|
|
|
let (range, description) = if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index)
|
|
{
|
|
if *c == '}' {
|
|
self.input_vec_index += 1;
|
|
return Some(r.clone());
|
|
}
|
|
// or r.clone()?
|
|
(r.start..r.start, format!("expected `}}`, found `{}`", c.escape_debug()))
|
|
} else {
|
|
(
|
|
// point at closing `"`
|
|
self.end_of_snippet..self.end_of_snippet,
|
|
"expected `}` but string was terminated".to_owned(),
|
|
)
|
|
};
|
|
|
|
let (note, secondary_label) = if arg.format.fill == Some('}') {
|
|
(
|
|
Some("the character `}` is interpreted as a fill character because of the `:` that precedes it".to_owned()),
|
|
arg.format.fill_span.clone().map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)),
|
|
)
|
|
} else {
|
|
(
|
|
Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
|
|
self.last_open_brace
|
|
.clone()
|
|
.map(|sp| ("because of this opening brace".to_owned(), sp)),
|
|
)
|
|
};
|
|
|
|
self.errors.push(ParseError {
|
|
description,
|
|
note,
|
|
label: "expected `}`".to_owned(),
|
|
span: range.start..range.start,
|
|
secondary_label,
|
|
suggestion: Suggestion::None,
|
|
});
|
|
|
|
None
|
|
}
|
|
|
|
/// Consumes all whitespace characters until the first non-whitespace character
|
|
fn ws(&mut self) {
|
|
let rest = &self.input_vec[self.input_vec_index..];
|
|
let step = rest.iter().position(|&(_, _, c)| !c.is_whitespace()).unwrap_or(rest.len());
|
|
self.input_vec_index += step;
|
|
}
|
|
|
|
/// Parses all of a string which is to be considered a "raw literal" in a
|
|
/// format string. This is everything outside of the braces.
|
|
fn string(&mut self, start: usize) -> &'a str {
|
|
while let Some((r, i, c)) = self.input_vec.get(self.input_vec_index) {
|
|
match c {
|
|
'{' | '}' => {
|
|
return &self.input[start..*i];
|
|
}
|
|
'\n' if self.is_source_literal => {
|
|
self.input_vec_index += 1;
|
|
self.line_spans.push(self.cur_line_start..r.start);
|
|
self.cur_line_start = r.end;
|
|
}
|
|
_ => {
|
|
self.input_vec_index += 1;
|
|
if self.is_source_literal && r.start == self.cur_line_start && c.is_whitespace()
|
|
{
|
|
self.cur_line_start = r.end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
&self.input[start..]
|
|
}
|
|
|
|
/// Parses an `Argument` structure, or what's contained within braces inside the format string.
|
|
fn argument(&mut self) -> Argument<'a> {
|
|
let start_idx = self.input_vec_index;
|
|
|
|
let position = self.position();
|
|
self.ws();
|
|
|
|
let end_idx = self.input_vec_index;
|
|
|
|
let format = match self.mode {
|
|
ParseMode::Format => self.format(),
|
|
ParseMode::InlineAsm => self.inline_asm(),
|
|
};
|
|
|
|
// Resolve position after parsing format spec.
|
|
let position = position.unwrap_or_else(|| {
|
|
let i = self.curarg;
|
|
self.curarg += 1;
|
|
ArgumentImplicitlyIs(i)
|
|
});
|
|
|
|
let position_span =
|
|
self.input_vec_index2range(start_idx).start..self.input_vec_index2range(end_idx).start;
|
|
Argument { position, position_span, format }
|
|
}
|
|
|
|
/// Parses a positional argument for a format. This could either be an
|
|
/// integer index of an argument, a named argument, or a blank string.
|
|
/// Returns `Some(parsed_position)` if the position is not implicitly
|
|
/// consuming a macro argument, `None` if it's the case.
|
|
fn position(&mut self) -> Option<Position<'a>> {
|
|
if let Some(i) = self.integer() {
|
|
Some(ArgumentIs(i.into()))
|
|
} else {
|
|
match self.input_vec.get(self.input_vec_index) {
|
|
Some((range, _, c)) if rustc_lexer::is_id_start(*c) => {
|
|
let start = range.start;
|
|
let word = self.word();
|
|
|
|
// Recover from `r#ident` in format strings.
|
|
// FIXME: use a let chain
|
|
if word == "r" {
|
|
if let Some((r, _, '#')) = self.input_vec.get(self.input_vec_index) {
|
|
if self
|
|
.input_vec
|
|
.get(self.input_vec_index + 1)
|
|
.is_some_and(|(_, _, c)| rustc_lexer::is_id_start(*c))
|
|
{
|
|
self.input_vec_index += 1;
|
|
let prefix_end = r.end;
|
|
let word = self.word();
|
|
let prefix_span = start..prefix_end;
|
|
let full_span =
|
|
start..self.input_vec_index2range(self.input_vec_index).start;
|
|
self.errors.insert(0, ParseError {
|
|
description: "raw identifiers are not supported".to_owned(),
|
|
note: Some("identifiers in format strings can be keywords and don't need to be prefixed with `r#`".to_string()),
|
|
label: "raw identifier used here".to_owned(),
|
|
span: full_span,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::RemoveRawIdent(prefix_span),
|
|
});
|
|
return Some(ArgumentNamed(word));
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(ArgumentNamed(word))
|
|
}
|
|
// This is an `ArgumentNext`.
|
|
// Record the fact and do the resolution after parsing the
|
|
// format spec, to make things like `{:.*}` work.
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn input_vec_index2pos(&self, index: usize) -> usize {
|
|
if let Some(&(_, pos, _)) = self.input_vec.get(index) { pos } else { self.input.len() }
|
|
}
|
|
|
|
fn input_vec_index2range(&self, index: usize) -> Range<usize> {
|
|
if let Some((r, _, _)) = self.input_vec.get(index) {
|
|
r.clone()
|
|
} else {
|
|
self.end_of_snippet..self.end_of_snippet
|
|
}
|
|
}
|
|
|
|
/// Parses a format specifier at the current position, returning all of the
|
|
/// relevant information in the `FormatSpec` struct.
|
|
fn format(&mut self) -> FormatSpec<'a> {
|
|
let mut spec = FormatSpec {
|
|
fill: None,
|
|
fill_span: None,
|
|
align: AlignUnknown,
|
|
sign: None,
|
|
alternate: false,
|
|
zero_pad: false,
|
|
debug_hex: None,
|
|
precision: CountImplied,
|
|
precision_span: None,
|
|
width: CountImplied,
|
|
width_span: None,
|
|
ty: &self.input[..0],
|
|
ty_span: None,
|
|
};
|
|
if !self.consume(':') {
|
|
return spec;
|
|
}
|
|
|
|
// fill character
|
|
if let Some(&(ref r, _, c)) = self.input_vec.get(self.input_vec_index) {
|
|
if let Some((_, _, '>' | '<' | '^')) = self.input_vec.get(self.input_vec_index + 1) {
|
|
self.input_vec_index += 1;
|
|
spec.fill = Some(c);
|
|
spec.fill_span = Some(r.clone());
|
|
}
|
|
}
|
|
// Alignment
|
|
if self.consume('<') {
|
|
spec.align = AlignLeft;
|
|
} else if self.consume('>') {
|
|
spec.align = AlignRight;
|
|
} else if self.consume('^') {
|
|
spec.align = AlignCenter;
|
|
}
|
|
// Sign flags
|
|
if self.consume('+') {
|
|
spec.sign = Some(Sign::Plus);
|
|
} else if self.consume('-') {
|
|
spec.sign = Some(Sign::Minus);
|
|
}
|
|
// Alternate marker
|
|
if self.consume('#') {
|
|
spec.alternate = true;
|
|
}
|
|
// Width and precision
|
|
let mut havewidth = false;
|
|
|
|
if let Some((range, _)) = self.consume_pos('0') {
|
|
// small ambiguity with '0$' as a format string. In theory this is a
|
|
// '0' flag and then an ill-formatted format string with just a '$'
|
|
// and no count, but this is better if we instead interpret this as
|
|
// no '0' flag and '0$' as the width instead.
|
|
if let Some((r, _)) = self.consume_pos('$') {
|
|
spec.width = CountIsParam(0);
|
|
spec.width_span = Some(range.start..r.end);
|
|
havewidth = true;
|
|
} else {
|
|
spec.zero_pad = true;
|
|
}
|
|
}
|
|
|
|
if !havewidth {
|
|
let start_idx = self.input_vec_index;
|
|
spec.width = self.count();
|
|
if spec.width != CountImplied {
|
|
let end = self.input_vec_index2range(self.input_vec_index).start;
|
|
spec.width_span = Some(self.input_vec_index2range(start_idx).start..end);
|
|
}
|
|
}
|
|
|
|
if let Some((range, _)) = self.consume_pos('.') {
|
|
if self.consume('*') {
|
|
// Resolve `CountIsNextParam`.
|
|
// We can do this immediately as `position` is resolved later.
|
|
let i = self.curarg;
|
|
self.curarg += 1;
|
|
spec.precision = CountIsStar(i);
|
|
} else {
|
|
spec.precision = self.count();
|
|
}
|
|
spec.precision_span =
|
|
Some(range.start..self.input_vec_index2range(self.input_vec_index).start);
|
|
}
|
|
|
|
let start_idx = self.input_vec_index;
|
|
// Optional radix followed by the actual format specifier
|
|
if self.consume('x') {
|
|
if self.consume('?') {
|
|
spec.debug_hex = Some(DebugHex::Lower);
|
|
spec.ty = "?";
|
|
} else {
|
|
spec.ty = "x";
|
|
}
|
|
} else if self.consume('X') {
|
|
if self.consume('?') {
|
|
spec.debug_hex = Some(DebugHex::Upper);
|
|
spec.ty = "?";
|
|
} else {
|
|
spec.ty = "X";
|
|
}
|
|
} else if let Some((range, _)) = self.consume_pos('?') {
|
|
spec.ty = "?";
|
|
if let Some((r, _, c)) = self.input_vec.get(self.input_vec_index) {
|
|
match c {
|
|
'#' | 'x' | 'X' => self.errors.insert(
|
|
0,
|
|
ParseError {
|
|
description: format!("expected `}}`, found `{c}`"),
|
|
note: None,
|
|
label: "expected `'}'`".into(),
|
|
span: r.clone(),
|
|
secondary_label: None,
|
|
suggestion: Suggestion::ReorderFormatParameter(
|
|
range.start..r.end,
|
|
format!("{c}?"),
|
|
),
|
|
},
|
|
),
|
|
_ => (),
|
|
}
|
|
}
|
|
} else {
|
|
spec.ty = self.word();
|
|
if !spec.ty.is_empty() {
|
|
let start = self.input_vec_index2range(start_idx).start;
|
|
let end = self.input_vec_index2range(self.input_vec_index).start;
|
|
spec.ty_span = Some(start..end);
|
|
}
|
|
}
|
|
spec
|
|
}
|
|
|
|
/// Parses an inline assembly template modifier at the current position, returning the modifier
|
|
/// in the `ty` field of the `FormatSpec` struct.
|
|
fn inline_asm(&mut self) -> FormatSpec<'a> {
|
|
let mut spec = FormatSpec {
|
|
fill: None,
|
|
fill_span: None,
|
|
align: AlignUnknown,
|
|
sign: None,
|
|
alternate: false,
|
|
zero_pad: false,
|
|
debug_hex: None,
|
|
precision: CountImplied,
|
|
precision_span: None,
|
|
width: CountImplied,
|
|
width_span: None,
|
|
ty: &self.input[..0],
|
|
ty_span: None,
|
|
};
|
|
if !self.consume(':') {
|
|
return spec;
|
|
}
|
|
|
|
let start_idx = self.input_vec_index;
|
|
spec.ty = self.word();
|
|
if !spec.ty.is_empty() {
|
|
let start = self.input_vec_index2range(start_idx).start;
|
|
let end = self.input_vec_index2range(self.input_vec_index).start;
|
|
spec.ty_span = Some(start..end);
|
|
}
|
|
|
|
spec
|
|
}
|
|
|
|
/// Parses a `Count` parameter at the current position. This does not check
|
|
/// for 'CountIsNextParam' because that is only used in precision, not
|
|
/// width.
|
|
fn count(&mut self) -> Count<'a> {
|
|
if let Some(i) = self.integer() {
|
|
if self.consume('$') { CountIsParam(i.into()) } else { CountIs(i) }
|
|
} else {
|
|
let start_idx = self.input_vec_index;
|
|
let word = self.word();
|
|
if word.is_empty() {
|
|
CountImplied
|
|
} else if let Some((r, _)) = self.consume_pos('$') {
|
|
CountIsName(word, self.input_vec_index2range(start_idx).start..r.start)
|
|
} else {
|
|
self.input_vec_index = start_idx;
|
|
CountImplied
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parses a word starting at the current position. A word is the same as a
|
|
/// Rust identifier, except that it can't start with `_` character.
|
|
fn word(&mut self) -> &'a str {
|
|
let index = self.input_vec_index;
|
|
match self.input_vec.get(self.input_vec_index) {
|
|
Some(&(ref r, i, c)) if rustc_lexer::is_id_start(c) => {
|
|
self.input_vec_index += 1;
|
|
(r.start, i)
|
|
}
|
|
_ => {
|
|
return "";
|
|
}
|
|
};
|
|
let (err_end, end): (usize, usize) = loop {
|
|
if let Some(&(ref r, i, c)) = self.input_vec.get(self.input_vec_index) {
|
|
if rustc_lexer::is_id_continue(c) {
|
|
self.input_vec_index += 1;
|
|
} else {
|
|
break (r.start, i);
|
|
}
|
|
} else {
|
|
break (self.end_of_snippet, self.input.len());
|
|
}
|
|
};
|
|
|
|
let word = &self.input[self.input_vec_index2pos(index)..end];
|
|
if word == "_" {
|
|
self.errors.push(ParseError {
|
|
description: "invalid argument name `_`".into(),
|
|
note: Some("argument name cannot be a single underscore".into()),
|
|
label: "invalid argument name".into(),
|
|
span: self.input_vec_index2range(index).start..err_end,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::None,
|
|
});
|
|
}
|
|
word
|
|
}
|
|
|
|
fn integer(&mut self) -> Option<u16> {
|
|
let mut cur: u16 = 0;
|
|
let mut found = false;
|
|
let mut overflow = false;
|
|
let start_index = self.input_vec_index;
|
|
while let Some(&(_, _, c)) = self.input_vec.get(self.input_vec_index) {
|
|
if let Some(i) = c.to_digit(10) {
|
|
self.input_vec_index += 1;
|
|
let (tmp, mul_overflow) = cur.overflowing_mul(10);
|
|
let (tmp, add_overflow) = tmp.overflowing_add(i as u16);
|
|
if mul_overflow || add_overflow {
|
|
overflow = true;
|
|
}
|
|
cur = tmp;
|
|
found = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if overflow {
|
|
let overflowed_int = &self.input[self.input_vec_index2pos(start_index)
|
|
..self.input_vec_index2pos(self.input_vec_index)];
|
|
self.errors.push(ParseError {
|
|
description: format!(
|
|
"integer `{}` does not fit into the type `u16` whose range is `0..={}`",
|
|
overflowed_int,
|
|
u16::MAX
|
|
),
|
|
note: None,
|
|
label: "integer out of range for `u16`".into(),
|
|
span: self.input_vec_index2range(start_index).start
|
|
..self.input_vec_index2range(self.input_vec_index).end,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::None,
|
|
});
|
|
}
|
|
|
|
found.then_some(cur)
|
|
}
|
|
|
|
fn suggest_format_debug(&mut self) {
|
|
if let (Some((range, _)), Some(_)) = (self.consume_pos('?'), self.consume_pos(':')) {
|
|
let word = self.word();
|
|
self.errors.insert(
|
|
0,
|
|
ParseError {
|
|
description: "expected format parameter to occur after `:`".to_owned(),
|
|
note: Some(format!("`?` comes after `:`, try `{}:{}` instead", word, "?")),
|
|
label: "expected `?` to occur after `:`".to_owned(),
|
|
span: range,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::None,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
fn suggest_format_align(&mut self, alignment: char) {
|
|
if let Some((range, _)) = self.consume_pos(alignment) {
|
|
self.errors.insert(
|
|
0,
|
|
ParseError {
|
|
description: "expected format parameter to occur after `:`".to_owned(),
|
|
note: None,
|
|
label: format!("expected `{}` to occur after `:`", alignment),
|
|
span: range,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::None,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) {
|
|
// If the argument is not an identifier, it is not a field access.
|
|
if !arg.is_identifier() {
|
|
return;
|
|
}
|
|
|
|
if let Some((_range, _pos)) = self.consume_pos('.') {
|
|
let field = self.argument();
|
|
// We can only parse simple `foo.bar` field access or `foo.0` tuple index access, any
|
|
// deeper nesting, or another type of expression, like method calls, are not supported
|
|
if !self.consume('}') {
|
|
return;
|
|
}
|
|
if let ArgumentNamed(_) = arg.position {
|
|
match field.position {
|
|
ArgumentNamed(_) => {
|
|
self.errors.insert(
|
|
0,
|
|
ParseError {
|
|
description: "field access isn't supported".to_string(),
|
|
note: None,
|
|
label: "not supported".to_string(),
|
|
span: arg.position_span.start..field.position_span.end,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::UsePositional,
|
|
},
|
|
);
|
|
}
|
|
ArgumentIs(_) => {
|
|
self.errors.insert(
|
|
0,
|
|
ParseError {
|
|
description: "tuple index access isn't supported".to_string(),
|
|
note: None,
|
|
label: "not supported".to_string(),
|
|
span: arg.position_span.start..field.position_span.end,
|
|
secondary_label: None,
|
|
suggestion: Suggestion::UsePositional,
|
|
},
|
|
);
|
|
}
|
|
_ => {}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assert a reasonable size for `Piece`
|
|
#[cfg(all(test, target_pointer_width = "64"))]
|
|
rustc_index::static_assert_size!(Piece<'_>, 16);
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|