refactor attributes once again

This commit is contained in:
Maybe Waffle 2022-08-28 15:07:19 +04:00
parent 25f9bff97a
commit c8514823d7
8 changed files with 302 additions and 129 deletions

View file

@ -1,96 +1,153 @@
use crate::Result;
use crate::{error::compile_error_at, Result};
use proc_macro2::Span;
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
Attribute, LitStr, Token,
spanned::Spanned,
Attribute, Ident, Lit, Path, Token,
};
pub(crate) enum CommandAttrName {
Prefix,
Description,
Rename,
ParseWith,
Separator,
pub(crate) fn fold_attrs<A, R>(
attrs: &[Attribute],
filter: fn(&Attribute) -> bool,
parse: impl Fn(Attr) -> Result<R>,
init: A,
f: impl Fn(A, R) -> Result<A>,
) -> Result<A> {
attrs
.iter()
.filter(|&a| filter(a))
.flat_map(|attribute| {
// FIXME: don't allocate here
let attrs =
match attribute.parse_args_with(|input: &ParseBuffer| {
input.parse_terminated::<_, Token![,]>(Attr::parse)
}) {
Ok(ok) => ok,
Err(err) => return vec![Err(err.into())],
};
attrs.into_iter().map(&parse).collect()
})
.try_fold(init, |acc, r| r.and_then(|r| f(acc, r)))
}
impl Parse for CommandAttrName {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name_arg: syn::Ident = input.parse()?;
/// An attribute key-value pair.
///
/// For example:
/// ```text
/// #[blahblah(key = "puff", value = 12, nope)]
/// ^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^
/// ```
pub(crate) struct Attr {
pub key: Ident,
pub value: AttrValue,
}
match name_arg.to_string().as_str() {
"prefix" => Ok(CommandAttrName::Prefix),
"description" => Ok(CommandAttrName::Description),
"rename" => Ok(CommandAttrName::Rename),
"parse_with" => Ok(CommandAttrName::ParseWith),
"separator" => Ok(CommandAttrName::Separator),
_ => Err(syn::Error::new(
name_arg.span(),
"unexpected attribute name (expected one of `prefix`, \
`description`, `rename`, `parse_with`, `separator`",
)),
/// Value of an attribute.
///
/// For example:
/// ```text
/// #[blahblah(key = "puff", value = 12, nope)]
/// ^^^^^^ ^^ ^-- (None pseudo-value)
/// ```
pub(crate) enum AttrValue {
Path(Path),
Lit(Lit),
None(Span),
}
impl Parse for Attr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let key = input.parse::<Ident>()?;
let value = match input.peek(Token![=]) {
true => {
input.parse::<Token![=]>()?;
input.parse::<AttrValue>()?
}
false => AttrValue::None(input.span()),
};
Ok(Self { key, value })
}
}
impl Attr {
pub(crate) fn span(&self) -> Span {
self.key.span().join(self.value.span()).unwrap_or(self.key.span())
}
}
impl AttrValue {
/// Unwraps this value if it's a string literal.
pub fn expect_string(self) -> Result<String> {
self.expect("a string", |this| match this {
AttrValue::Lit(Lit::Str(s)) => Ok(s.value()),
_ => Err(this),
})
}
// /// Unwraps this value if it's a path.
// pub fn expect_path(self) -> Result<Path> {
// self.expect("a path", |this| match this {
// AttrValue::Path(p) => Ok(p),
// _ => Err(this),
// })
// }
fn expect<T>(
self,
expected: &str,
f: impl FnOnce(Self) -> Result<T, Self>,
) -> Result<T> {
f(self).map_err(|this| {
compile_error_at(
&format!("expected {expected}, found {}", this.descr()),
this.span(),
)
})
}
fn descr(&self) -> &'static str {
use Lit::*;
match self {
Self::None(_) => "nothing",
Self::Lit(l) => match l {
Str(_) | ByteStr(_) => "a string",
Char(_) => "a character",
Byte(_) | Int(_) => "an integer",
Float(_) => "a floating point integer",
Bool(_) => "a boolean",
Verbatim(_) => ":shrug:",
},
Self::Path(_) => "a path",
}
}
/// Returns span of the value
///
/// ```text
/// #[blahblah(key = "puff", value = 12, nope )]
/// ^^^^^^ ^^ ^
/// ```
fn span(&self) -> Span {
match self {
Self::Path(p) => p.span(),
Self::Lit(l) => l.span(),
Self::None(sp) => *sp,
}
}
}
pub(crate) struct CommandAttr {
pub name: CommandAttrName,
pub value: String,
}
impl Parse for CommandAttr {
impl Parse for AttrValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse::<CommandAttrName>()?;
let this = match input.peek(Lit) {
true => Self::Lit(input.parse()?),
false => Self::Path(input.parse()?),
};
// FIXME: this should support value-less attrs, as well as
// non-string-literal values
input.parse::<Token![=]>()?;
let value = input.parse::<LitStr>()?.value();
Ok(Self { name, value })
}
}
pub(crate) struct CommandAttrs(Vec<CommandAttr>);
impl CommandAttrs {
pub fn from_attributes(attributes: &[Attribute]) -> Result<Self> {
let mut attrs = Vec::new();
for attribute in attributes.iter().filter(is_command_attribute) {
let attrs_ = attribute.parse_args_with(|input: &ParseBuffer| {
input.parse_terminated::<_, Token![,]>(CommandAttr::parse)
})?;
attrs.extend(attrs_);
}
Ok(Self(attrs))
}
}
impl<'a> IntoIterator for &'a CommandAttrs {
type Item = &'a CommandAttr;
type IntoIter = std::slice::Iter<'a, CommandAttr>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl IntoIterator for CommandAttrs {
type Item = CommandAttr;
type IntoIter = std::vec::IntoIter<CommandAttr>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
fn is_command_attribute(a: &&Attribute) -> bool {
match a.path.get_ident() {
Some(ident) => ident == "command",
_ => false,
Ok(this)
}
}

View file

@ -1,5 +1,5 @@
use crate::{
attr::CommandAttrs, command::Command, command_enum::CommandEnum,
command::Command, command_attr::CommandAttrs, command_enum::CommandEnum,
compile_error, fields_parse::impl_parse_args, unzip::Unzip, Result,
};

View file

@ -1,9 +1,6 @@
use crate::{
attr::{self, CommandAttr, CommandAttrName},
command_enum::CommandEnum,
fields_parse::ParserType,
rename_rules::RenameRule,
Result,
command_attr::CommandAttrs, command_enum::CommandEnum,
fields_parse::ParserType, rename_rules::RenameRule, Result,
};
pub(crate) struct Command {
@ -14,8 +11,7 @@ pub(crate) struct Command {
}
impl Command {
pub fn try_from(attrs: attr::CommandAttrs, name: &str) -> Result<Self> {
let attrs = parse_attrs(attrs)?;
pub fn try_from(attrs: CommandAttrs, name: &str) -> Result<Self> {
let CommandAttrs {
prefix,
description,
@ -24,7 +20,7 @@ impl Command {
separator: _,
} = attrs;
let name = rename_rule.apply(name);
let name = rename_rule.unwrap_or(RenameRule::Identity).apply(name);
Ok(Self { prefix, description, parser, name })
}
@ -60,33 +56,3 @@ impl Command {
self.description != Some("off".to_owned())
}
}
pub(crate) struct CommandAttrs {
pub prefix: Option<String>,
pub description: Option<String>,
pub rename_rule: RenameRule,
pub parser: Option<ParserType>,
pub separator: Option<String>,
}
pub(crate) fn parse_attrs(attrs: attr::CommandAttrs) -> Result<CommandAttrs> {
let mut prefix = None;
let mut description = None;
let mut rename_rule = RenameRule::Identity;
let mut parser = None;
let mut separator = None;
for CommandAttr { name, value } in attrs {
match name {
CommandAttrName::Prefix => prefix = Some(value),
CommandAttrName::Description => description = Some(value),
CommandAttrName::Rename => rename_rule = RenameRule::parse(&value)?,
CommandAttrName::ParseWith => {
parser = Some(ParserType::parse(&value))
}
CommandAttrName::Separator => separator = Some(value),
}
}
Ok(CommandAttrs { prefix, description, rename_rule, parser, separator })
}

115
src/command_attr.rs Normal file
View file

@ -0,0 +1,115 @@
use crate::{
attr::{fold_attrs, Attr},
error::compile_error_at,
fields_parse::ParserType,
rename_rules::RenameRule,
Result,
};
use proc_macro2::Span;
use syn::Attribute;
/// Attributes for `BotCommands` derive macro.
pub(crate) struct CommandAttrs {
pub prefix: Option<String>,
pub description: Option<String>,
pub rename_rule: Option<RenameRule>,
pub parser: Option<ParserType>,
pub separator: Option<String>,
}
/// An attribute for `BotCommands` derive macro.
pub(crate) struct CommandAttr {
kind: CommandAttrKind,
sp: Span,
}
pub(crate) enum CommandAttrKind {
Prefix(String),
Description(String),
Rename(RenameRule),
ParseWith(ParserType),
Separator(String),
}
impl CommandAttrs {
pub fn from_attributes(attributes: &[Attribute]) -> Result<Self> {
use CommandAttrKind::*;
fold_attrs(
attributes,
is_command_attribute,
CommandAttr::parse,
Self {
prefix: None,
description: None,
rename_rule: None,
parser: None,
separator: None,
},
|mut this, attr| {
fn insert<T>(
opt: &mut Option<T>,
x: T,
sp: Span,
) -> Result<()> {
match opt {
slot @ None => {
*slot = Some(x);
Ok(())
}
Some(_) => {
Err(compile_error_at("duplicate attribute", sp))
}
}
}
match attr.kind {
Prefix(p) => insert(&mut this.prefix, p, attr.sp),
Description(d) => insert(&mut this.description, d, attr.sp),
Rename(r) => insert(&mut this.rename_rule, r, attr.sp),
ParseWith(p) => insert(&mut this.parser, p, attr.sp),
Separator(s) => insert(&mut this.separator, s, attr.sp),
}?;
Ok(this)
},
)
}
}
impl CommandAttr {
fn parse(attr: Attr) -> Result<Self> {
use CommandAttrKind::*;
let sp = attr.span();
let Attr { key, value } = attr;
let kind = match &*key.to_string() {
"prefix" => Prefix(value.expect_string()?),
"description" => Description(value.expect_string()?),
"rename" => Rename(
value.expect_string().and_then(|r| RenameRule::parse(&r))?,
),
"parse_with" => {
ParseWith(value.expect_string().map(|p| ParserType::parse(&p))?)
}
"separator" => Separator(value.expect_string()?),
_ => {
return Err(compile_error_at(
"unexpected attribute name (expected one of `prefix`, \
`description`, `rename`, `parse_with` and `separator`",
key.span(),
))
}
};
Ok(Self { kind, sp })
}
}
fn is_command_attribute(a: &Attribute) -> bool {
match a.path.get_ident() {
Some(ident) => ident == "command",
_ => false,
}
}

View file

@ -1,5 +1,5 @@
use crate::{
attr, command::parse_attrs, fields_parse::ParserType,
command_attr::CommandAttrs, fields_parse::ParserType,
rename_rules::RenameRule, Result,
};
@ -12,14 +12,17 @@ pub(crate) struct CommandEnum {
}
impl CommandEnum {
pub fn try_from(attrs: attr::CommandAttrs) -> Result<Self> {
let attrs = parse_attrs(attrs)?;
pub fn try_from(attrs: CommandAttrs) -> Result<Self> {
let CommandAttrs {
prefix,
description,
rename_rule,
parser,
separator,
} = attrs;
let mut parser = parser.unwrap_or(ParserType::Default);
let prefix = attrs.prefix;
let description = attrs.description;
let rename = attrs.rename_rule;
let separator = attrs.separator;
let mut parser = attrs.parser.unwrap_or(ParserType::Default);
// FIXME: Error on unused separator
if let (ParserType::Split { separator }, Some(s)) =
(&mut parser, &separator)
{
@ -28,7 +31,7 @@ impl CommandEnum {
Ok(Self {
prefix,
description,
rename_rule: rename,
rename_rule: rename_rule.unwrap_or(RenameRule::Identity),
parser_type: parser,
})
}

View file

@ -1,4 +1,4 @@
use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
@ -13,6 +13,36 @@ where
Error(quote! { compile_error! { #data } })
}
pub(crate) fn compile_error_at(msg: &str, sp: Span) -> Error {
use proc_macro2::{
Delimiter, Group, Ident, Literal, Punct, Spacing, TokenTree,
};
use std::iter::FromIterator;
// compile_error! { $msg }
let ts = TokenStream::from_iter(vec![
TokenTree::Ident(Ident::new("compile_error", sp)),
TokenTree::Punct({
let mut punct = Punct::new('!', Spacing::Alone);
punct.set_span(sp);
punct
}),
TokenTree::Group({
let mut group = Group::new(Delimiter::Brace, {
TokenStream::from_iter(vec![TokenTree::Literal({
let mut string = Literal::string(msg);
string.set_span(sp);
string
})])
});
group.set_span(sp);
group
}),
]);
Error(ts)
}
impl From<Error> for proc_macro2::TokenStream {
fn from(Error(e): Error) -> Self {
e

View file

@ -9,6 +9,7 @@ pub(crate) enum ParserType {
}
impl ParserType {
// FIXME: use path for custom
pub fn parse(data: &str) -> Self {
match data {
"default" => ParserType::Default,

View file

@ -5,6 +5,7 @@ extern crate proc_macro;
mod attr;
mod bot_commands;
mod command;
mod command_attr;
mod command_enum;
mod error;
mod fields_parse;