use crate::Argument;
use proc_macro2::{Punct, Spacing, Span, TokenStream, TokenTree};
#[derive(Debug)]
enum State {
Start,
Ident(proc_macro2::Ident),
Assign(proc_macro2::Ident, proc_macro2::Punct),
Done,
Next(proc_macro2::Punct),
End,
Error(ErrorStruct),
}
#[derive(Debug)]
pub(crate) struct ErrorStruct(proc_macro2::Span, &'static str);
impl From<ErrorStruct> for syn::Error {
fn from(e: ErrorStruct) -> syn::Error {
let ErrorStruct(span, message) = e;
syn::Error::new(span, message.to_string())
}
}
const EOF: char = ';';
macro_rules! build_error {
($p:ident, $($body:tt)*) => {
State::Error(ErrorStruct($p.span(), $($body)*))
}
}
fn state_machine_transfer(tt: TokenTree, state: State, ret: &mut Vec<Argument>) -> State {
match state {
State::Start => match tt {
TokenTree::Ident(i) => State::Ident(i),
TokenTree::Punct(p) => {
if p.as_char() == EOF {
State::End
} else {
build_error!(p, "Expecting an identifier.")
}
}
_ => build_error!(tt, "Expecting an identifier or empty list."),
},
State::Ident(i) => match tt {
TokenTree::Punct(p) => match p.as_char() {
'=' => State::Assign(i, p),
',' => {
ret.push(Argument::Switch {
name: i.to_string(),
});
State::Next(p)
}
EOF => {
ret.push(Argument::Switch {
name: i.to_string(),
});
State::End
}
_ => build_error!(p, "Expecting '=', ',', or end of argument list."),
},
TokenTree::Group(g) => match parse_arguments(g.stream(), g.span()) {
Ok(args) => {
ret.push(Argument::Function {
name: i.to_string(),
args: args,
});
State::Done
}
Err(e) => State::Error(e),
},
_ => build_error!(tt, "Expecting '=', ',', or end of argument list."),
},
State::Assign(i, _p) => match tt {
TokenTree::Ident(to_i) => {
ret.push(Argument::Flag {
key: i.to_string(),
value: to_i.to_string(),
});
State::Done
}
TokenTree::Literal(to_i) => {
let string_value = to_i.to_string();
ret.push(Argument::Flag {
key: i.to_string(),
value: string_value[1..string_value.len() - 1].into(),
});
State::Done
}
_ => build_error!(tt, "Expecting an identifier or a string literal."),
},
State::Done => {
if let TokenTree::Punct(p) = tt {
match p.as_char() {
',' => State::Next(p),
EOF => State::End,
_ => build_error!(p, "Expecting ',' or end of argument list."),
}
} else {
build_error!(tt, "Expecting ',' or end of argument list.")
}
}
State::Next(_p) => {
if let TokenTree::Ident(i) = tt {
State::Ident(i)
} else {
build_error!(tt, "Expecting an identifier.")
}
}
State::Error(_) => state,
State::End => panic!("Reached end state but still receives more token: {}", tt),
}
}
pub(crate) fn parse_arguments(
attr: TokenStream,
outer_span: Span,
) -> Result<Vec<Argument>, ErrorStruct> {
let mut ret = vec![];
let mut state = State::Start;
for tt in attr.into_iter() {
state = state_machine_transfer(tt, state, &mut ret);
}
let mut eof = TokenTree::Punct(Punct::new(EOF, Spacing::Alone));
eof.set_span(outer_span);
state = state_machine_transfer(eof, state, &mut ret);
match state {
State::Error(e) => return Err(e),
State::End => (),
_ => panic!("Reached non-end state {:?}", state),
}
return Ok(ret);
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
macro_rules! parse_quote {
($($body:tt)*) => ({
let expr = quote! {
$($body)*
};
parse_arguments(expr.into(), Span::call_site())
.expect(concat!("Unexpected error when parsing {", stringify!($($body)*), "}"))
})
}
macro_rules! parse_quote_error {
($($body:tt)*) => ({
let expr = quote! {
$($body)*
};
parse_arguments(expr.into(), Span::call_site()).err()
.expect(concat!("Expecting error when parsing {", stringify!($($body)*), "}"))
})
}
macro_rules! assert_parse_quote_error_msg_eq {
($msg:expr, $($body:tt)*) => ({
let ErrorStruct(_,message) = parse_quote_error!($($body)*);
assert_eq!($msg, message);
})
}
#[test]
fn test_empty() {
let args = parse_quote! {};
assert_eq!(0, args.len());
}
#[test]
fn test_switch() {
let args = parse_quote! { TurnThisOn };
assert_eq!(1, args.len());
assert_eq!(
Argument::Switch {
name: "TurnThisOn".into()
},
args[0]
);
}
#[test]
fn test_flag() {
let args = parse_quote! {
SetValue = tts, SetStr = "123"
};
assert_eq!(2, args.len());
assert_eq!(
Argument::Flag {
key: "SetValue".into(),
value: "tts".into()
},
args[0]
);
assert_eq!(
Argument::Flag {
key: "SetStr".into(),
value: "123".into()
},
args[1]
);
}
#[test]
fn test_function() {
let args = parse_quote! { CallThisFunc() };
assert_eq!(1, args.len());
assert_eq!(
Argument::Function {
name: "CallThisFunc".into(),
args: vec![]
},
args[0]
);
}
#[test]
fn test_function_args() {
let args = parse_quote! { CallThisFunc(DoX, Doy) };
assert_eq!(1, args.len());
assert_eq!(
Argument::Function {
name: "CallThisFunc".into(),
args: vec![
Argument::Switch { name: "DoX".into() },
Argument::Switch { name: "Doy".into() }
]
},
args[0]
);
}
#[test]
fn test_nested_functions() {
let args = parse_quote! { CallThisFunc(DoX, CallAnotherFunc(Doy), DoZ) };
assert_eq!(1, args.len());
assert_eq!(
Argument::Function {
name: "CallThisFunc".into(),
args: vec![
Argument::Switch { name: "DoX".into() },
Argument::Function {
name: "CallAnotherFunc".into(),
args: vec![Argument::Switch { name: "Doy".into() }]
},
Argument::Switch { name: "DoZ".into() }
]
},
args[0]
);
}
#[test]
fn test_error_in_the_middle() {
let ErrorStruct(_, message) = parse_quote_error! { A, , A };
assert_eq!("Expecting an identifier.", message);
}
#[test]
fn test_error_at_the_end() {
let ErrorStruct(_, message) = parse_quote_error! { A, };
assert_eq!("Expecting an identifier.", message);
}
#[test]
fn test_all() {
let args = parse_quote! {
Switch0,
Switch1,
CallThisFunc(Switch8, Flag = "f", NestedFunc(PP)),
AnotherFlag = VV,
OneMore = WW,
Switch7,
PlusOne = "ZZZ",
CallAnotherFunc(DD=AAA),
Join(),
Switch9
};
assert_eq!(
vec![
Argument::Switch {
name: "Switch0".into()
},
Argument::Switch {
name: "Switch1".into()
},
Argument::Function {
name: "CallThisFunc".into(),
args: vec![
Argument::Switch {
name: "Switch8".into()
},
Argument::Flag {
key: "Flag".into(),
value: "f".into()
},
Argument::Function {
name: "NestedFunc".into(),
args: vec![Argument::Switch { name: "PP".into() }]
}
]
},
Argument::Flag {
key: "AnotherFlag".into(),
value: "VV".into()
},
Argument::Flag {
key: "OneMore".into(),
value: "WW".into()
},
Argument::Switch {
name: "Switch7".into()
},
Argument::Flag {
key: "PlusOne".into(),
value: "ZZZ".into()
},
Argument::Function {
name: "CallAnotherFunc".into(),
args: vec![Argument::Flag {
key: "DD".into(),
value: "AAA".into()
}]
},
Argument::Function {
name: "Join".into(),
args: vec![]
},
Argument::Switch {
name: "Switch9".into()
}
],
args
);
}
#[test]
fn test_state_machine() {
parse_quote! { A };
parse_quote! {};
assert_parse_quote_error_msg_eq! {"Expecting an identifier.", : };
assert_parse_quote_error_msg_eq! {"Expecting an identifier or empty list.", () };
parse_quote! { A = a };
parse_quote! { A, A };
parse_quote! { A };
assert_parse_quote_error_msg_eq! {"Expecting '=', ',', or end of argument list.", A : };
parse_quote! { A () };
assert_parse_quote_error_msg_eq! {"Expecting an identifier.", A (:) };
assert_parse_quote_error_msg_eq! {"Expecting an identifier.", A ( B ( C, :) ) };
assert_parse_quote_error_msg_eq! {"Expecting '=', ',', or end of argument list.", A A };
parse_quote! { A = a };
parse_quote! { A = "a" };
assert_parse_quote_error_msg_eq! {"Expecting an identifier or a string literal.", A = () };
parse_quote! { A = A, A };
parse_quote! { A = A };
assert_parse_quote_error_msg_eq! {"Expecting ',' or end of argument list.", A = A: };
assert_parse_quote_error_msg_eq! {"Expecting ',' or end of argument list.", A = a () };
parse_quote! { A = A, A };
assert_parse_quote_error_msg_eq! {"Expecting an identifier.", A = A, () };
assert_parse_quote_error_msg_eq! {"Expecting an identifier.", A, (), A, A, A};
}
#[test]
#[should_panic]
fn test_end_end() {
parse_quote_error! { ; A };
}
}