extern crate proc_macro;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
export::Span,
parse::{Parse, ParseStream},
Error, Expr, ExprGroup, ExprLit, ExprRange, Item, Lit, LitByteStr, LitStr, RangeLimits, Token,
};
enum Str {
Chars(LitStr),
Bytes(LitByteStr),
}
impl Str {
pub fn len(&self) -> usize {
match self {
Str::Bytes(lit) => lit.value().len(),
Str::Chars(lit) => lit.value().len(),
}
}
pub fn span(&self) -> Span {
match self {
Str::Bytes(lit) => lit.span(),
Str::Chars(lit) => lit.span(),
}
}
pub fn kind(&self) -> &str {
match self {
Str::Bytes(_) => "byte-string",
Str::Chars(_) => "string",
}
}
}
struct Args {
lit: Str,
min: usize,
max: usize,
}
fn parse_opt_usize(boxed: &Option<Box<Expr>>, default: usize) -> Result<usize, Error> {
let expr = match boxed {
Some(expr) => &**expr,
None => return Ok(default),
};
if let Expr::Lit(ExprLit {
lit: Lit::Int(i), ..
}) = expr
{
i.base10_parse::<usize>()
} else {
Err(Error::new_spanned(boxed, ""))
}
}
fn parse_range(
from: Option<Box<Expr>>,
to: Option<Box<Expr>>,
limits: RangeLimits,
) -> Result<(usize, usize), Error> {
let from = match parse_opt_usize(&from, 0) {
Ok(from) => from,
_ => return Err(Error::new_spanned(from, "must range over usize values")),
};
let to = match (parse_opt_usize(&to, usize::MAX), limits) {
(Ok(to), RangeLimits::HalfOpen(_)) => to - 1,
(Ok(to), RangeLimits::Closed(_)) => to,
_ => return Err(Error::new_spanned(from, "must range over usize values")),
};
Ok((from, to))
}
macro_rules! lit {
($typ:ident, $pat:ident) => {
Expr::Lit(ExprLit { lit: Lit::$typ($pat), .. })
};
}
macro_rules! range {
($from:ident, $to:ident, $limits:ident) => {
ExprRange {$from, $to, $limits, .. }
}
}
#[proc_macro]
pub fn assert_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
match assert_len_impl(input) {
Ok(res) => res.into(),
Err(err) => err.to_compile_error().into(),
}
}
struct Inputs {
literal: Expr,
range: ExprRange,
first: Option<Item>,
second: Option<Item>,
}
fn strip_invisible_group(expr: Expr) -> Expr {
match expr {
Expr::Group(ExprGroup { expr, .. }) => *expr,
e => e,
}
}
impl Parse for Inputs {
fn parse(input: ParseStream) -> Result<Self, Error> {
let literal: Expr = strip_invisible_group(input.parse()?);
input.parse::<Token![,]>()?;
let range: ExprRange = match strip_invisible_group(input.parse()?) {
Expr::Range(e) => e,
other => return Err(Error::new_spanned(other, "expected range here")),
};
let mut first: Option<Item> = None;
let mut second: Option<Item> = None;
if input.parse::<Token![,]>().is_err() {
return Ok(Inputs {
literal,
range,
first,
second,
});
}
first = Some(input.parse()?);
input.parse::<Token![,]>()?;
second = Some(input.parse()?);
Ok(Inputs {
literal,
range,
first,
second,
})
}
}
fn assert_len_impl(input: proc_macro::TokenStream) -> Result<TokenStream, Error> {
let Inputs {
literal,
range,
first,
second,
} = syn::parse(input)?;
let parsed = match (literal, range) {
(lit!(Str, s), range!(from, to, limits)) => {
let (from, to) = parse_range(from, to, limits)?;
Some(Args {
lit: Str::Chars(s),
min: from,
max: to,
})
}
(lit!(ByteStr, s), range!(from, to, limits)) => {
let (from, to) = parse_range(from, to, limits)?;
Some(Args {
lit: Str::Bytes(s),
min: from,
max: to,
})
}
_ => None,
};
if parsed.is_none() {
if let Some(second) = second {
return Ok(second.into_token_stream());
}
return Err(Error::new(
Span::call_site(),
"argument must be a tuple of string and usize range",
));
}
let Args { lit, min, max } = parsed.unwrap();
let len = lit.len();
let mut error = TokenStream::new();
if len < min {
error.extend(
Error::new(
lit.span(),
format!(
"{} of length {} not allowed here, need at least {}",
lit.kind(),
len,
min
),
)
.to_compile_error(),
);
}
if len > max {
error.extend(
Error::new(
lit.span(),
format!(
"{} of length {} not allowed here, need at most {}",
lit.kind(),
len,
max
),
)
.to_compile_error(),
);
}
Ok(first
.map(|f| {
let first = f.into_token_stream();
quote!(#error #first)
})
.unwrap_or_else(|| if error.is_empty() { quote!(()) } else { error }))
}