#![warn(rust_2021_compatibility, rustdoc::all, missing_docs)]
use ansi_term::Color::Red;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use std::iter::once;
use syn::{
braced, parse, parse::Parse, parse_macro_input, Attribute, FieldsNamed, Generics, Ident,
LitInt, LitStr, Token, Visibility,
};
struct ErrorVariant {
name: Ident,
fields: FieldsNamed,
msg: LitStr,
}
impl Parse for ErrorVariant {
fn parse(input: parse::ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
let fields = input.parse()?;
let msg = input.parse()?;
Ok(Self { name, fields, msg })
}
}
impl ErrorVariant {
fn to_tokens(&self, code: &str, tokens: &mut TokenStream2) {
let name = &self.name;
let fields = &self.fields;
let msg = &self.msg;
let code = format!("{}: ", code);
tokens.extend(quote! {
#[doc = #code]
#[doc = #msg]
#name #fields,
})
}
}
impl ErrorVariant {
fn get_field_names<'s>(&'s self) -> impl Iterator<Item = &'s Ident> {
self.fields
.named
.iter()
.map(|field| field.ident.as_ref().unwrap())
}
fn format(&self, code: &str) -> TokenStream2 {
let name = &self.name;
let fields = self.get_field_names();
let msg = &self.msg;
quote! {
Self::#name { #(#fields, )* } => {
::core::write!{f, "{}: ", #code}?;
::core::write!{f, #msg}?;
::core::result::Result::Ok(())
}
}
}
}
enum ErrorTree {
Prefix(LitInt, LitStr, Vec<ErrorTree>),
Variant(LitInt, ErrorVariant),
}
impl Parse for ErrorTree {
fn parse(input: parse::ParseStream) -> syn::Result<Self> {
if input.peek2(LitStr) {
let code = input.parse()?;
let desc = input.parse()?;
let children;
braced!(children in input);
let mut nodes = Vec::new();
while !children.is_empty() {
let node = children.parse()?;
nodes.push(node);
}
Ok(ErrorTree::Prefix(code, desc, nodes))
} else {
let code = input.parse()?;
let variant = input.parse()?;
let _comma: Token![,] = input.parse()?;
Ok(ErrorTree::Variant(code, variant))
}
}
}
impl ErrorTree {
fn get_variants<'s>(
&'s self,
prefix: String,
) -> impl Iterator<Item = (String, &'s ErrorVariant)> {
match self {
Self::Prefix(code, _desc, children) => children
.iter()
.flat_map(|node| node.get_variants(format!("{prefix}{}", code.to_string())))
.collect::<Vec<_>>()
.into_iter(),
Self::Variant(code, var) => {
let prefix = format!("{prefix}{code}");
vec![(prefix, var)].into_iter()
}
}
}
fn get_nodes<'s>(
&'s self,
prefix: &str,
depth: usize,
) -> impl Iterator<Item = (usize, String, Option<String>, String)> {
match self {
Self::Prefix(code, desc, children) => {
let prefix = format!("{}{}", prefix, code);
once((depth, prefix.clone(), None, desc.value()))
.chain(
children
.iter()
.flat_map(|node| node.get_nodes(&prefix, depth + 1)),
)
.collect::<Vec<_>>()
.into_iter()
}
Self::Variant(code, var) => {
let prefix = format!("{}{}", prefix, code);
vec![(depth, prefix, Some(var.name.to_string()), var.msg.value())].into_iter()
}
}
}
}
struct ErrorEnum {
attrs: Vec<Attribute>,
vis: Visibility,
name: Ident,
generics: Generics,
variants: Vec<(Ident, LitStr, Vec<ErrorTree>)>,
}
impl ErrorEnum {
fn get_variants<'s>(&'s self) -> impl Iterator<Item = (String, &'s ErrorVariant)> {
self.variants.iter().flat_map(|(ident, _, tree)| {
tree.iter()
.flat_map(|node| node.get_variants(ident.to_string()))
})
}
fn get_nodes<'s>(&'s self) -> Vec<(usize, String, Option<String>, String)> {
self.variants
.iter()
.flat_map(|(ident, msg, tree)| {
let prefix = ident.to_string();
once((0, prefix.clone(), None, msg.value())).chain(
tree.iter()
.flat_map(|node| node.get_nodes(&prefix, 1))
.collect::<Vec<_>>()
.into_iter(),
)
})
.collect()
}
}
impl Parse for ErrorEnum {
fn parse(input: parse::ParseStream) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let vis = input.parse()?;
let name = input.parse()?;
let generics = input.parse()?;
let mut variants = Vec::new();
while !input.is_empty() {
let kind = input.parse()?;
let msg = input.parse()?;
let inner;
braced!(inner in input);
let mut trees = Vec::new();
while !inner.is_empty() {
let tree = inner.parse()?;
trees.push(tree);
}
assert!(inner.is_empty());
variants.push((kind, msg, trees));
}
Ok(Self {
attrs,
vis,
generics,
name,
variants,
})
}
}
impl ToTokens for ErrorEnum {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let attrs = &self.attrs;
let vis = &self.vis;
let name = &self.name;
let generics = &self.generics;
let doc = self
.get_nodes()
.into_iter()
.map(|(depth, code, name, desc)| {
let indent = " ".repeat(depth);
if let Some(name) = name {
format!("{indent}- `{code}`(**{name}**): {desc}")
} else {
format!("{indent}- `{code}`: {desc}")
}
});
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let variants = {
let mut tokens = TokenStream2::new();
self.get_variants()
.for_each(|(code, var)| var.to_tokens(&code, &mut tokens));
tokens
};
tokens.extend(quote! {
#[doc = "List of error variants:"]
#(
#[doc = #doc]
)*
#(#attrs)*
#vis enum #name #generics {
#variants
}
});
let variants = self.get_variants();
let branches = variants.map(|(code, variant)| {
let code = format!("error[{}]", &code);
#[cfg(feature = "colored")]
let code = Red.paint(code).to_string();
variant.format(&code)
});
tokens.extend(quote! {
impl #impl_generics core::fmt::Display for #name #ty_generics #where_clause {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
#(#branches)*
}
}
}
});
let variants = self.get_variants();
let branches = variants.map(|(code, variant)| {
let name = &variant.name;
quote! {
Self::#name { .. } => #code,
}
});
tokens.extend(quote! {
impl #impl_generics #name #ty_generics #where_clause {
pub fn get_code(&self) -> &'static str {
match self {
#(#branches)*
}
}
}
});
}
}
#[proc_macro]
pub fn error_type(token: TokenStream) -> TokenStream {
let error = parse_macro_input!(token as ErrorEnum);
error.to_token_stream().into()
}
#[cfg(test)]
mod tests {
use crate::ErrorEnum;
use quote::{quote, ToTokens};
#[test]
fn test() {
let output: ErrorEnum = syn::parse2(quote! {
FileSystemError
E01 FileNotFound {path: std::path::Path}
"{path} not found.",
})
.unwrap();
let output = output.into_token_stream();
eprintln!("{:#}", output);
}
}