use proc_macro::{
Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree,
};
use quote::quote;
#[proc_macro_attribute]
pub fn resext(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut errs = vec![];
let input = match EnumInput::from_tokens(item, &mut errs) {
Some(i) => i,
None => return errs.into_iter().collect(),
};
let original: &proc_macro2::TokenStream = &input.original.into();
let args = parse_args(attr, &mut errs).unwrap_or_default();
let manual_name = &input.name;
let enum_name = proc_macro2::Ident::new(
manual_name.to_string().as_str(),
manual_name.span().into(),
);
let vis: &proc_macro2::TokenStream = &input.vis.into();
let alias = args.alias.unwrap_or_else(|| String::from("Res"));
let struct_name = quote::format_ident!("{}Err", alias);
let buf_name = quote::format_ident!("{}Buf", alias);
let trait_name = quote::format_ident!("{}Ext", alias);
let alias = quote::format_ident!("{}", alias);
let alloc = args.alloc;
let variants = &input.variants;
let include_variant = args.include_variant;
let display_match_arms = variants.iter().map(|variant| {
let variant_name = proc_macro2::Ident::new(variant.name.to_string().as_str(), variant.name.span().into());
match &variant.kind {
VariantKind::Unnamed(_) => {
if include_variant {
quote! {
#enum_name::#variant_name(var) => write!(f, "{}: {}", stringify!(#variant_name), var),
}
} else {
quote! {
#enum_name::#variant_name(var) => write!(f, "{}", var),
}
}
}
VariantKind::Named(variant_field, _) => {
let variant_field = proc_macro2::Ident::new(variant_field.to_string().as_str(), variant_field.span().into());
if include_variant {
quote! {
#enum_name::#variant_name { #variant_field } => write!(f, "{}: {}: {}", stringify!(#variant_name), stringify!(#variant_field), #variant_field),
}
} else {
quote! {
#enum_name::#variant_name { #variant_field } => write!(f, "{}", #variant_field),
}
}
}
VariantKind::Unit => {
quote! {
#enum_name::#variant_name => write!(f, "{}", stringify!(#variant_name)),
}
}
}
});
let from_impls = variants.iter().filter_map(|variant| {
let variant_name = proc_macro2::Ident::new(variant.name.to_string().as_str(), variant.name.span().into());
match &variant.kind {
VariantKind::Unnamed(field_type) => {
let field_type: proc_macro2::TokenStream = field_type.clone().into();
Some(quote! {
impl From<#field_type> for #enum_name {
fn from(value: #field_type) -> Self {
Self::#variant_name(value)
}
}
impl From<#field_type> for #struct_name {
fn from(value: #field_type) -> Self {
Self { msg: #buf_name::new(), source: #enum_name::#variant_name(value) }
}
}
})
}
VariantKind::Named(field_name, field_type) => {
let field_type: proc_macro2::TokenStream = field_type.clone().into();
let field_name = proc_macro2::Ident::new(field_name.to_string().as_str(), field_name.span().into());
Some(quote! {
impl From<#field_type> for #enum_name {
fn from(value: #field_type) -> Self {
Self::#variant_name { #field_name: value }
}
}
impl From<#field_type> for #struct_name {
fn from(value: #field_type) -> Self {
Self { msg: #buf_name::new(), source: #enum_name::#variant_name { #field_name: value } }
}
}
})
}
_ => None,
}
});
let prefix = args.prefix.unwrap_or_default();
let suffix = args.suffix.unwrap_or_default();
let msg_prefix = args.msg_prefix.unwrap_or_default();
let msg_suffix = args.msg_suffix.unwrap_or_default();
let delimiter = args.delimiter.unwrap_or_else(|| String::from("\n - "));
let source_prefix =
args.source_prefix.unwrap_or_else(|| String::from("Error: "));
let buf_size = args.buf_size.unwrap_or(64);
let gen_buf = {
if !alloc {
quote! {
struct #buf_name {
curr_pos: u16,
buf: [u8; #buf_size],
truncate: bool,
}
impl #buf_name {
fn new() -> Self {
Self { buf: [0; #buf_size], curr_pos: 0, truncate: false }
}
fn get_slice(&self) -> &[u8] {
&self.buf[..self.curr_pos as usize]
}
fn is_empty(&self) -> bool {
self.curr_pos == 0
}
fn truncate(&self) -> bool {
self.truncate
}
}
impl core::fmt::Write for #buf_name {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let bytes = s.as_bytes();
let pos = self.curr_pos as usize;
let cap = #buf_size - pos;
let limit = if cap < bytes.len() {
self.truncate = true;
cap
} else {
bytes.len()
};
let to_copy = match bytes[..limit]
.iter()
.rposition(|&b| (b & 0xC0) != 0x80)
{
Some(start_of_last_char) => {
let last_char_byte = bytes[start_of_last_char];
let width = match last_char_byte {
0..=127 => 1,
192..=223 => 2,
224..=239 => 3,
240..=247 => 4,
_ => 1,
};
if start_of_last_char + width <= limit {
start_of_last_char + width
} else {
start_of_last_char
}
}
None => 0,
};
self.buf[pos..pos + to_copy].copy_from_slice(&bytes[..to_copy]);
self.curr_pos += to_copy as u16;
Ok(())
}
}
}
} else {
quote! {
mod __private_alloc {
extern crate alloc;
pub(crate) use alloc::vec::Vec;
}
enum #buf_name {
Stack { buf: [u8; #buf_size], curr_pos: u16 },
Heap(__private_alloc::Vec<u8>),
}
impl #buf_name {
fn new() -> Self {
Self::Stack { buf: [0; #buf_size], curr_pos: 0 }
}
fn get_slice(&self) -> &[u8] {
match self {
Self::Stack { buf, curr_pos } => &buf[..*curr_pos as usize],
Self::Heap(buf) => buf,
}
}
fn truncate(&self) -> bool {
false
}
fn is_empty(&self) -> bool {
match self {
Self::Heap(buf) => buf.is_empty(),
Self::Stack { buf: _, curr_pos } => *curr_pos == 0,
}
}
}
impl core::fmt::Write for #buf_name {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
match self {
Self::Heap(buf) => buf.extend_from_slice(s.as_bytes()),
Self::Stack { buf, curr_pos } => {
let bytes = s.as_bytes();
let pos = *curr_pos as usize;
let cap = #buf_size - pos;
if bytes.len() > cap {
{
extern crate alloc;
let mut vec = alloc::vec::Vec::new();
vec.reserve_exact(pos + bytes.len());
vec.extend_from_slice(&buf[..pos]);
vec.extend_from_slice(bytes);
*self = #buf_name::Heap(vec);
}
} else {
buf[pos..pos + bytes.len()].copy_from_slice(bytes);
*curr_pos += bytes.len() as u16;
}
}
}
Ok(())
}
}
}
}
};
let expanded = quote! {
#[derive(Debug)]
#original
impl core::fmt::Display for #enum_name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
#(#display_match_arms)*
}
}
}
#[doc(hidden)]
#vis struct #struct_name {
msg: #buf_name,
#vis source: #enum_name
}
impl core::error::Error for #struct_name {}
impl core::fmt::Write for #struct_name {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if s.is_empty() {
Ok(())
} else {
self.msg.write_str(s)
}
}
}
impl core::fmt::Display for #struct_name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.msg.is_empty() {
write!(f, "{}{}{}", #source_prefix, &self.source, #suffix)
} else {
write!(
f,
"{}{}{}\n{}{}{}",
#prefix,
unsafe { core::str::from_utf8_unchecked(&self.msg.get_slice()) },
if self.msg.truncate() { "..." } else { "" },
#source_prefix,
self.source,
#suffix,
)
}
}
}
impl core::fmt::Debug for #struct_name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.msg.is_empty() {
write!(f, "{}{:?}{}", #source_prefix, &self.source, #suffix)
} else {
write!(
f,
"{}{}{}\n{}{:?}{}",
#prefix,
unsafe { core::str::from_utf8_unchecked(&self.msg.get_slice()) },
if self.msg.truncate() { "..." } else { "" },
#source_prefix,
self.source,
#suffix,
)
}
}
}
impl #struct_name {
#[doc(hidden)]
#vis fn new<E>(msg: &str, source: E) -> Self where #enum_name: From<E> {
use core::fmt::Write;
let mut buf = #buf_name::new();
let _ = buf.write_str(msg);
Self { msg: buf, source: #enum_name::from(source) }
}
#[doc(hidden)]
#vis fn from_args<E, F: FnOnce(#struct_name, &str, &str, &str) -> #struct_name>(msg: F, source: E) -> Self where #enum_name: From<E> {
use core::fmt::Write;
let err = Self { msg: #buf_name::new(), source: #enum_name::from(source) };
msg(err, "", "", "")
}
}
impl From<#enum_name> for #struct_name {
fn from(value: #enum_name) -> Self {
Self { msg: #buf_name::new(), source: value }
}
}
#(#from_impls)*
#[doc(hidden)]
#vis trait #trait_name<T, S> {
#[doc(hidden)]
fn context(self, msg: S) -> Result<T, #struct_name>;
}
impl<T> #trait_name<T, &str> for Result<T, #struct_name> {
fn context(self, msg: &str) -> Result<T, #struct_name> {
match self {
Ok(ok) => Ok(ok),
Err(mut err) => {
use core::fmt::Write;
if err.msg.is_empty() {
let _ = err.write_str(msg);
} else {
let _ = err.write_str(#delimiter);
let _ = err.write_str(#msg_prefix);
let _ = err.write_str(msg);
let _ = err.write_str(#msg_suffix);
}
Err(err)
}
}
}
}
impl<T, E> #trait_name<T, &str> for Result<T, E> where #enum_name: From<E> {
fn context(self, msg: &str) -> Result<T, #struct_name> {
match self {
Ok(ok) => Ok(ok),
Err(err) => Err(#struct_name::new(msg, err)),
}
}
}
impl<'a, T, F: FnOnce(#struct_name, &'a str, &'a str, &'a str) -> #struct_name> #trait_name<T, F> for Result<T, #struct_name> {
fn context(self, msg: F) -> Result<T, #struct_name> {
match self {
Ok(ok) => Ok(ok),
Err(mut err) => {
use core::fmt::Write;
let err = if err.msg.is_empty() {
msg(err, "", "", "")
} else {
msg(err, #delimiter, #msg_prefix, #msg_suffix)
};
Err(err)
}
}
}
}
impl<'a, T, F: FnOnce(#struct_name, &'a str, &'a str, &'a str) -> #struct_name, E> #trait_name<T, F> for Result<T, E> where #enum_name: From<E> {
fn context(self, msg: F) -> Result<T, #struct_name> {
match self {
Ok(ok) => Ok(ok),
Err(err) => {
use core::fmt::Write;
let buf = #buf_name::new();
let mut err = #struct_name { msg: buf, source: #enum_name::from(err) };
let err = msg(err, "", "", "");
Err(err)
}
}
}
}
#vis type #alias<T> = Result<T, #struct_name>;
#gen_buf
};
if !errs.is_empty() {
errs.into_iter().collect()
} else {
TokenStream::from(expanded)
}
}
struct EnumInput {
vis: TokenStream,
name: proc_macro::Ident,
variants: Vec<Variant>,
original: TokenStream,
}
struct Variant {
name: proc_macro::Ident,
kind: VariantKind,
}
enum VariantKind {
Unnamed(TokenStream),
Named(proc_macro::Ident, TokenStream),
Unit,
}
impl EnumInput {
fn from_tokens(
tokens: TokenStream,
errs: &mut Vec<TokenStream>,
) -> Option<Self> {
let original = tokens.clone();
let mut iter = tokens.into_iter().peekable();
let mut vis = TokenStream::new();
loop {
match iter.peek() {
Some(TokenTree::Ident(i))
if i.to_string().as_str() == "enum" =>
{
break;
}
Some(_) => vis.extend(iter.next()),
None => {
errs.push(construct_err(
"expected enum item",
Span::call_site(),
));
break;
}
}
}
match iter.next() {
Some(TokenTree::Ident(i)) if i.to_string().as_str() == "enum" => {}
Some(tt) => errs.push(construct_err(
&format!("expected enum keyword, found: {}", tt),
tt.span(),
)),
None => errs.push(construct_err(
"expected enum keyword",
Span::call_site(),
)),
}
let mut name = proc_macro::Ident::new("Var", Span::call_site());
match iter.next() {
Some(TokenTree::Ident(i)) => name = i,
Some(tt) => errs.push(construct_err(
&format!("expected enum identifier, found: {}", tt),
tt.span(),
)),
None => errs.push(construct_err(
"expected enum item name",
Span::call_site(),
)),
}
let body = match iter.next() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
Some(g)
}
Some(tt) => {
errs.push(construct_err("expected enum body", tt.span()));
None
}
None => {
errs.push(construct_err(
"expected enum body",
Span::call_site(),
));
None
}
};
let variants = Self::parse_variants(body, errs);
if errs.is_empty() {
Some(EnumInput { vis, name, variants, original })
} else {
None
}
}
fn parse_variants(
group: Option<Group>,
errs: &mut Vec<TokenStream>,
) -> Vec<Variant> {
if group.is_none() {
return vec![];
}
let mut iter =
unsafe { group.unwrap_unchecked() }.stream().into_iter().peekable();
let mut out = vec![];
let mut first = true;
while let Some(token) = iter.next() {
let token = if first {
first = false;
token
} else {
match token {
TokenTree::Punct(p) if p.as_char() == ',' => {}
_ => errs.push(construct_err(
"expected comma separator between enum variants",
token.span(),
)),
}
match iter.next() {
Some(tt) => tt,
None => break,
}
};
let name = match token {
TokenTree::Ident(i) => i,
_ => {
errs.push(construct_err(
"expected variant name identifier",
token.span(),
));
proc_macro::Ident::new("Var", Span::call_site())
}
};
let mut kind = VariantKind::Unit;
match iter.peek() {
Some(TokenTree::Punct(p)) if p.as_char() == ',' => {}
Some(TokenTree::Group(g))
if g.delimiter() == Delimiter::Parenthesis =>
{
let stream = g.stream().into_iter();
iter.next();
let mut ty = TokenStream::new();
for token in stream {
ty.extend([token]);
}
if !ty.is_empty() {
kind = VariantKind::Unnamed(ty);
}
}
Some(TokenTree::Group(g))
if g.delimiter() == Delimiter::Brace =>
{
let mut stream = g.stream().into_iter();
iter.next();
let token = stream.next();
let name = match token {
Some(TokenTree::Ident(i)) => i,
_ => {
errs.push(construct_err(
"expected field name identifier",
Span::call_site(),
));
proc_macro::Ident::new("Var", Span::call_site())
}
};
match stream.next() {
Some(TokenTree::Punct(p)) if p.as_char() == ':' => {}
Some(tt) =>
errs.push(construct_err(
&format!("expected field name colon separator, found: {}", tt),
tt.span()
)),
_ =>
errs.push(construct_err(
"expected field name colon separator",
Span::call_site(),
)),
}
let mut ty = TokenStream::new();
for token in stream {
ty.extend([token]);
}
if !ty.is_empty() {
kind = VariantKind::Named(name, ty);
}
}
None => {}
Some(tt) => errs.push(construct_err(
"expected named or unnamed variant",
tt.span(),
)),
}
out.push(Variant { name, kind });
}
out
}
}
#[derive(Default)]
struct ResExtArgs {
prefix: Option<String>,
suffix: Option<String>,
msg_prefix: Option<String>,
msg_suffix: Option<String>,
delimiter: Option<String>,
source_prefix: Option<String>,
include_variant: bool,
alias: Option<String>,
buf_size: Option<usize>,
alloc: bool,
}
fn parse_args(
input: TokenStream,
errs: &mut Vec<TokenStream>,
) -> Option<ResExtArgs> {
let mut args = ResExtArgs {
prefix: None,
suffix: None,
msg_prefix: None,
msg_suffix: None,
delimiter: None,
source_prefix: None,
include_variant: false,
alias: None,
buf_size: None,
alloc: false,
};
let mut iter = input.into_iter().peekable();
while let Some(token) = iter.next() {
let key = match token {
TokenTree::Ident(ident) => Some(ident),
_ => {
errs.push(construct_err(
&format!("expected key identifier, found: {}", token),
token.span(),
));
None
}
};
let token = iter.next();
match token {
Some(TokenTree::Punct(p)) if p.as_char() == '=' => {}
_ => {
errs.push(construct_err(
&format!(
"expected '=' separator between key and value, found: '{}'",
token.as_ref().map(|s| s.to_string()).unwrap_or_default()
),
token.map(|t| t.span()).unwrap_or(Span::call_site()),
));
}
}
let val = iter.next();
if val.is_none() {
errs.push(construct_err(
"expected value after '='",
Span::call_site(),
));
}
if let Some(key) = key {
match key.to_string().as_str() {
"prefix" => {
args.prefix = val
.map(|s| s.to_string().trim_matches('"').to_string());
}
"suffix" => {
args.suffix = val
.map(|s| s.to_string().trim_matches('"').to_string());
}
"msg_prefix" => {
args.msg_prefix = val
.map(|s| s.to_string().trim_matches('"').to_string());
}
"msg_suffix" => {
args.msg_suffix = val
.map(|s| s.to_string().trim_matches('"').to_string());
}
"delimiter" => {
args.delimiter = val
.map(|s| s.to_string().trim_matches('"').to_string());
}
"source_prefix" => {
args.source_prefix = val
.map(|s| s.to_string().trim_matches('"').to_string());
}
"include_variant" => {
let v = val
.as_ref()
.map(|t| t.to_string())
.unwrap_or_default()
.parse();
args.include_variant = match v {
Ok(v) => v,
Err(err) => {
errs.push(construct_err(&format!("invalid bool value for `include_variant` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site)));
false
}
};
}
"alias" => {
args.alias = val
.map(|s| s.to_string().trim_matches('"').to_string());
}
"buf_size" => {
let v = val
.as_ref()
.map(|t| t.to_string())
.unwrap_or_default()
.parse();
args.buf_size = match v {
Ok(v) => Some(v),
Err(err) => {
errs.push(construct_err(&format!("invalid usize value for `buf_size` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site) ));
None
}
};
}
"alloc" => {
let v = val
.as_ref()
.map(|t| t.to_string())
.unwrap_or_default()
.parse();
args.alloc = match v {
Ok(v) => v,
Err(err) => {
errs.push(construct_err(&format!("invalid bool value for `alloc` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site)));
false
}
};
}
_ => errs.push(construct_err(
&format!("unrecognized attribute: {}", &key),
key.span(),
)),
}
}
match iter.peek() {
Some(TokenTree::Punct(p)) if p.as_char() == ',' => {
iter.next();
}
Some(TokenTree::Ident(_)) => {}
None => {}
_ => {
errs.push(
construct_err(
&format!("expected comma, newline or whitespace delimiter, found: '{}'", iter.peek().map(|t| t.to_string()).unwrap_or_default()),
iter.peek().map(|t| t.span()).unwrap_or_else(Span::call_site)
),
);
iter.next();
}
}
}
if !errs.is_empty() { None } else { Some(args) }
}
fn construct_err(msg: &str, span: Span) -> TokenStream {
let mut ts = TokenStream::new();
ts.extend([TokenTree::Ident(proc_macro::Ident::new(
"compile_error",
span,
))]);
ts.extend([TokenTree::Punct(Punct::new('!', Spacing::Alone))]);
let mut inner = TokenStream::new();
let mut lit = Literal::string(msg);
lit.set_span(span);
inner.extend([TokenTree::Literal(lit)]);
ts.extend([TokenTree::Group(Group::new(Delimiter::Parenthesis, inner))]);
ts.extend([TokenTree::Punct(Punct::new(';', Spacing::Alone))]);
ts
}