mirror of https://github.com/zkat/miette.git
236 lines
7.9 KiB
Rust
236 lines
7.9 KiB
Rust
// NOTE: Most code in this file is taken straight from `thiserror`.
|
|
use std::collections::HashSet as Set;
|
|
use std::iter::FromIterator;
|
|
|
|
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
|
|
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
|
use syn::ext::IdentExt;
|
|
use syn::parse::{ParseStream, Parser};
|
|
use syn::{braced, bracketed, parenthesized, Ident, Index, LitStr, Member, Result, Token};
|
|
|
|
#[derive(Clone)]
|
|
pub struct Display {
|
|
pub fmt: LitStr,
|
|
pub args: TokenStream,
|
|
pub has_bonus_display: bool,
|
|
}
|
|
|
|
impl ToTokens for Display {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
let fmt = &self.fmt;
|
|
let args = &self.args;
|
|
tokens.extend(quote! {
|
|
write!(__formatter, #fmt #args)
|
|
});
|
|
}
|
|
}
|
|
|
|
impl Display {
|
|
// Transform `"error {var}"` to `"error {}", var`.
|
|
pub fn expand_shorthand(&mut self, members: &Set<Member>) {
|
|
let raw_args = self.args.clone();
|
|
let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
|
|
|
|
let span = self.fmt.span();
|
|
let fmt = self.fmt.value();
|
|
let mut read = fmt.as_str();
|
|
let mut out = String::new();
|
|
let mut args = self.args.clone();
|
|
let mut has_bonus_display = false;
|
|
|
|
let mut has_trailing_comma = false;
|
|
if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
|
|
if punct.as_char() == ',' {
|
|
has_trailing_comma = true;
|
|
}
|
|
}
|
|
|
|
while let Some(brace) = read.find('{') {
|
|
out += &read[..brace + 1];
|
|
read = &read[brace + 1..];
|
|
if read.starts_with('{') {
|
|
out.push('{');
|
|
read = &read[1..];
|
|
continue;
|
|
}
|
|
let next = match read.chars().next() {
|
|
Some(next) => next,
|
|
None => return,
|
|
};
|
|
let member = match next {
|
|
'0'..='9' => {
|
|
let int = take_int(&mut read);
|
|
let member = match int.parse::<u32>() {
|
|
Ok(index) => Member::Unnamed(Index { index, span }),
|
|
Err(_) => return,
|
|
};
|
|
if !members.contains(&member) {
|
|
out += ∫
|
|
continue;
|
|
}
|
|
member
|
|
}
|
|
'a'..='z' | 'A'..='Z' | '_' => {
|
|
let mut ident = take_ident(&mut read);
|
|
ident.set_span(span);
|
|
Member::Named(ident)
|
|
}
|
|
_ => continue,
|
|
};
|
|
let local = match &member {
|
|
Member::Unnamed(index) => format_ident!("_{}", index),
|
|
Member::Named(ident) => ident.clone(),
|
|
};
|
|
let mut formatvar = local.clone();
|
|
if formatvar.to_string().starts_with("r#") {
|
|
formatvar = format_ident!("r_{}", formatvar);
|
|
}
|
|
if formatvar.to_string().starts_with('_') {
|
|
// Work around leading underscore being rejected by 1.40 and
|
|
// older compilers. https://github.com/rust-lang/rust/pull/66847
|
|
formatvar = format_ident!("field_{}", formatvar);
|
|
}
|
|
out += &formatvar.to_string();
|
|
if !named_args.insert(formatvar.clone()) {
|
|
// Already specified in the format argument list.
|
|
continue;
|
|
}
|
|
if !has_trailing_comma {
|
|
args.extend(quote_spanned!(span=> ,));
|
|
}
|
|
args.extend(quote_spanned!(span=> #formatvar = #local));
|
|
if read.starts_with('}') && members.contains(&member) {
|
|
has_bonus_display = true;
|
|
// args.extend(quote_spanned!(span=> .as_display()));
|
|
}
|
|
has_trailing_comma = false;
|
|
}
|
|
|
|
out += read;
|
|
self.fmt = LitStr::new(&out, self.fmt.span());
|
|
self.args = args;
|
|
self.has_bonus_display = has_bonus_display;
|
|
}
|
|
}
|
|
|
|
fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
|
|
let mut named_args = Set::new();
|
|
|
|
while !input.is_empty() {
|
|
if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
|
|
input.parse::<Token![,]>()?;
|
|
let ident = input.call(Ident::parse_any)?;
|
|
input.parse::<Token![=]>()?;
|
|
named_args.insert(ident);
|
|
} else {
|
|
input.parse::<TokenTree>()?;
|
|
}
|
|
}
|
|
|
|
Ok(named_args)
|
|
}
|
|
|
|
fn take_int(read: &mut &str) -> String {
|
|
let mut int = String::new();
|
|
for (i, ch) in read.char_indices() {
|
|
match ch {
|
|
'0'..='9' => int.push(ch),
|
|
_ => {
|
|
*read = &read[i..];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
int
|
|
}
|
|
|
|
fn take_ident(read: &mut &str) -> Ident {
|
|
let mut ident = String::new();
|
|
let raw = read.starts_with("r#");
|
|
if raw {
|
|
ident.push_str("r#");
|
|
*read = &read[2..];
|
|
}
|
|
for (i, ch) in read.char_indices() {
|
|
match ch {
|
|
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
|
|
_ => {
|
|
*read = &read[i..];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Ident::parse_any.parse_str(&ident).unwrap()
|
|
}
|
|
|
|
pub fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> {
|
|
let mut tokens = Vec::new();
|
|
while !input.is_empty() {
|
|
if begin_expr && input.peek(Token![.]) {
|
|
if input.peek2(Ident) {
|
|
input.parse::<Token![.]>()?;
|
|
begin_expr = false;
|
|
continue;
|
|
}
|
|
if input.peek2(syn::LitInt) {
|
|
input.parse::<Token![.]>()?;
|
|
let int: Index = input.parse()?;
|
|
let ident = format_ident!("_{}", int.index, span = int.span);
|
|
tokens.push(TokenTree::Ident(ident));
|
|
begin_expr = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
begin_expr = input.peek(Token![break])
|
|
|| input.peek(Token![continue])
|
|
|| input.peek(Token![if])
|
|
|| input.peek(Token![in])
|
|
|| input.peek(Token![match])
|
|
|| input.peek(Token![mut])
|
|
|| input.peek(Token![return])
|
|
|| input.peek(Token![while])
|
|
|| input.peek(Token![+])
|
|
|| input.peek(Token![&])
|
|
|| input.peek(Token![!])
|
|
|| input.peek(Token![^])
|
|
|| input.peek(Token![,])
|
|
|| input.peek(Token![/])
|
|
|| input.peek(Token![=])
|
|
|| input.peek(Token![>])
|
|
|| input.peek(Token![<])
|
|
|| input.peek(Token![|])
|
|
|| input.peek(Token![%])
|
|
|| input.peek(Token![;])
|
|
|| input.peek(Token![*])
|
|
|| input.peek(Token![-]);
|
|
|
|
let token: TokenTree = if input.peek(syn::token::Paren) {
|
|
let content;
|
|
let delimiter = parenthesized!(content in input);
|
|
let nested = parse_token_expr(&content, true)?;
|
|
let mut group = Group::new(Delimiter::Parenthesis, nested);
|
|
group.set_span(delimiter.span.join());
|
|
TokenTree::Group(group)
|
|
} else if input.peek(syn::token::Brace) {
|
|
let content;
|
|
let delimiter = braced!(content in input);
|
|
let nested = parse_token_expr(&content, true)?;
|
|
let mut group = Group::new(Delimiter::Brace, nested);
|
|
group.set_span(delimiter.span.join());
|
|
TokenTree::Group(group)
|
|
} else if input.peek(syn::token::Bracket) {
|
|
let content;
|
|
let delimiter = bracketed!(content in input);
|
|
let nested = parse_token_expr(&content, true)?;
|
|
let mut group = Group::new(Delimiter::Bracket, nested);
|
|
group.set_span(delimiter.span.join());
|
|
TokenTree::Group(group)
|
|
} else {
|
|
input.parse()?
|
|
};
|
|
tokens.push(token);
|
|
}
|
|
Ok(TokenStream::from_iter(tokens))
|
|
}
|