mirror of
https://github.com/PluralKit/PluralKit.git
synced 2026-02-14 09:40:10 +00:00
make the proc macro DSL parser far more readable
This commit is contained in:
parent
fce23c2b90
commit
737d6d3216
3 changed files with 90 additions and 49 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -475,6 +475,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ proc-macro = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
syn = "2.0"
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,95 @@
|
||||||
use proc_macro2::{Delimiter, TokenStream, TokenTree, Ident, Literal, Span};
|
use proc_macro2::{Delimiter, TokenStream, TokenTree, Literal, Span};
|
||||||
use quote::quote;
|
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
||||||
|
use syn::{parse_macro_input, Token, Ident};
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
|
||||||
fn make_command(
|
enum CommandToken {
|
||||||
tokens: Vec<TokenStream>,
|
/// "typed argument" being a member of the `Token` enum in the
|
||||||
help: Literal,
|
/// command parser crate.
|
||||||
cb: Literal,
|
///
|
||||||
) -> TokenStream {
|
/// prefixed with `@` in the command macro.
|
||||||
quote! {
|
TypedArgument(Ident, Span),
|
||||||
Command { tokens: vec![#(#tokens),*], help: #help.to_string(), cb: #cb.to_string() }
|
|
||||||
|
/// interpreted as a literal string in the command input.
|
||||||
|
///
|
||||||
|
/// no prefix in the command macro.
|
||||||
|
Literal(Literal, Span),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for CommandToken {
|
||||||
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
|
let lookahead = input.lookahead1();
|
||||||
|
if lookahead.peek(Token![@]) {
|
||||||
|
// typed argument
|
||||||
|
input.parse::<Token![@]>()?;
|
||||||
|
let ident = input.parse::<Ident>()?;
|
||||||
|
Ok(Self::TypedArgument(ident.clone(), ident.span()))
|
||||||
|
} else if lookahead.peek(Ident) {
|
||||||
|
// literal string
|
||||||
|
let ident = input.parse::<Ident>()?;
|
||||||
|
let lit = Literal::string(&format!("{ident}"));
|
||||||
|
Ok(Self::Literal(lit, ident.span()))
|
||||||
|
} else {
|
||||||
|
Err(input.error("expected a command token"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command_from_stream(stream: TokenStream) -> TokenStream {
|
impl Into<TokenStream> for CommandToken {
|
||||||
let mut part = 0;
|
fn into(self) -> TokenStream {
|
||||||
let mut found_tokens: Vec<TokenStream> = Vec::new();
|
match self {
|
||||||
let mut found_cb: Option<Literal> = None;
|
Self::TypedArgument(ident, span) => quote_spanned! {span=>
|
||||||
let mut found_help: Option<Literal> = None;
|
Token::#ident
|
||||||
|
},
|
||||||
|
|
||||||
let mut is_token_lit = false;
|
Self::Literal(lit, span) => quote_spanned! {span=>
|
||||||
let mut tokens = stream.clone().into_iter();
|
Token::Value(vec![ #lit.to_string(), ])
|
||||||
'a: loop {
|
},
|
||||||
let cur_token = tokens.next();
|
}.into()
|
||||||
match cur_token {
|
}
|
||||||
None if part == 2 && found_help.is_some() => break 'a,
|
}
|
||||||
Some(TokenTree::Ident(ident)) if part == 0 => {
|
|
||||||
found_tokens.push(if is_token_lit {
|
struct Command {
|
||||||
quote! { Token::#ident }.into()
|
tokens: Vec<CommandToken>,
|
||||||
} else {
|
help: Literal,
|
||||||
let lit = Literal::string(&format!("{ident}"));
|
cb: Literal,
|
||||||
quote! { Token::Value(vec![#lit.to_string() ]) }
|
}
|
||||||
});
|
|
||||||
// reset this
|
impl Parse for Command {
|
||||||
is_token_lit = false;
|
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||||
|
let mut tokens = Vec::<CommandToken>::new();
|
||||||
|
loop {
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Some(TokenTree::Punct(punct)) if part == 0 && format!("{punct}") == "@" => {
|
|
||||||
is_token_lit = true
|
tokens.push(input.parse::<CommandToken>()?);
|
||||||
}
|
}
|
||||||
Some(TokenTree::Punct(punct))
|
input.parse::<Token![,]>()?;
|
||||||
if ((part == 0 && found_tokens.len() > 0) || (part == 1 && found_cb.is_some()))
|
|
||||||
&& format!("{punct}") == "," =>
|
let cb_ident = input.parse::<Ident>()?;
|
||||||
{
|
let cb = Literal::string(&format!("{cb_ident}"));
|
||||||
part += 1
|
input.parse::<Token![,]>()?;
|
||||||
}
|
|
||||||
Some(TokenTree::Ident(ident)) if part == 1 => {
|
let help = input.parse::<Literal>()?;
|
||||||
found_cb = Some(Literal::string(&format!("{ident}")))
|
|
||||||
}
|
Ok(Self {
|
||||||
Some(TokenTree::Literal(lit)) if part == 2 => {
|
tokens,
|
||||||
found_help = Some(lit)
|
cb,
|
||||||
}
|
help,
|
||||||
_ => panic!("invalid command definition: {stream}"),
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<TokenStream> for Command {
|
||||||
|
fn into(self) -> TokenStream {
|
||||||
|
let Self { tokens, help, cb } = self;
|
||||||
|
let tokens = tokens.into_iter().map(Into::into).collect::<Vec<TokenStream>>();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
Command { tokens: vec![#(#tokens),*], help: #help.to_string(), cb: #cb.to_string() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
make_command(found_tokens, found_help.unwrap(), found_cb.unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
|
|
@ -70,7 +108,8 @@ pub fn commands(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
//
|
//
|
||||||
match top_level_tokens.next() {
|
match top_level_tokens.next() {
|
||||||
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
|
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
|
||||||
commands.push(command_from_stream(group.stream()));
|
let group_stream: proc_macro::TokenStream = group.stream().into();
|
||||||
|
commands.push(parse_macro_input!(group_stream as Command).into());
|
||||||
}
|
}
|
||||||
_ => panic!("contents of commands! macro is invalid"),
|
_ => panic!("contents of commands! macro is invalid"),
|
||||||
}
|
}
|
||||||
|
|
@ -83,8 +122,8 @@ pub fn commands(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
|
||||||
let command_registrations = commands
|
let command_registrations = commands
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| -> proc_macro2::TokenStream { quote! { tree.register_command(#v); }.into() })
|
.map(|v| -> TokenStream { quote! { tree.register_command(#v); }.into() })
|
||||||
.collect::<proc_macro2::TokenStream>();
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
let res = quote! {
|
let res = quote! {
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue