crate::ix!();
#[derive(Getters, Debug)]
#[getset(get="pub")]
pub struct OperatorItemsParser {
items: Vec<OperatorSpecItem>,
}
impl syn::parse::Parse for OperatorItemsParser {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
info!("OperatorItemsParser::parse: START");
info!(" remaining input = '{}'", input.to_string());
let mut items = Vec::new();
while !input.is_empty() {
if !input.peek(syn::Ident) {
let leftover = input.to_string();
let msg = format!("unexpected token(s) leftover: {}", leftover);
info!(" returning error => {}", msg);
return Err(syn::Error::new(input.span(), msg));
}
let ident_op: syn::Ident = input.parse()?;
if ident_op != "op" {
let msg = format!("Expected `op`, found `{}`", ident_op);
info!(" returning error => {}", msg);
return Err(syn::Error::new(ident_op.span(), msg));
}
let eq_tok: syn::Token![=] = input.parse()?;
info!(" parsed '=' token: {:?}", eq_tok.span());
let lit_str: syn::LitStr = input.parse()?;
let path_str = lit_str.value();
info!(" parsed lit_str => {:?}", path_str);
let path = match syn::parse_str::<syn::Path>(&path_str) {
Ok(p) => {
info!(" parsed Path => {}", quote::ToTokens::to_token_stream(&p));
p
}
Err(_err) => {
let msg = format!("Could not parse `{}` as a Path.", path_str);
info!(" returning error => {}", msg);
return Err(syn::Error::new(lit_str.span(), msg));
}
};
items.push(OperatorSpecItem::new(path));
if input.peek(syn::Token![,]) {
let _comma: syn::Token![,] = input.parse()?;
info!(" consumed trailing comma, continuing parse");
} else {
break;
}
}
if !input.is_empty() {
let leftover = input.to_string();
let msg = format!("unexpected token(s) leftover: {}", leftover);
info!(" returning error => {}", msg);
return Err(syn::Error::new(input.span(), msg));
}
info!(
"OperatorItemsParser::parse: FINISH, parsed {} items",
items.len()
);
Ok(OperatorItemsParser { items })
}
}
#[cfg(test)]
mod test_operator_items_parser_code {
use super::*;
use syn::parse::Parser;
use syn::{parse_quote, DeriveInput};
pub fn parse_available_operators_attribute(
di: &DeriveInput
) -> Result<(Vec<OperatorSpecItem>, syn::Generics), proc_macro2::TokenStream> {
info!("parse_available_operators_attribute: START");
let attr = match di.attrs.iter().find(|a| a.path().is_ident("available_operators")) {
Some(a) => a,
None => {
let err = quote::quote_spanned! { di.ident.span() =>
compile_error!("Missing `#[available_operators(...)]` attribute!");
};
info!(" returning error => Missing `#[available_operators(...)]` attribute!");
return Err(err);
}
};
let parser = OperatorItemsParser::parse;
let parsed_items_parser = match attr.parse_args_with(OperatorItemsParser::parse) {
Ok(p) => p,
Err(e) => {
let e_str = e.to_string();
let err = quote::quote_spanned! { attr.span() =>
compile_error!(#e_str);
};
return Err(err);
}
};
info!(" parse_available_operators_attribute: SUCCESS => parsed {} items",
parsed_items_parser.items.len());
Ok((parsed_items_parser.items, di.generics.clone()))
}
#[test]
fn test_parse_ok() {
info!("test_parse_ok: START");
let di: DeriveInput = parse_quote! {
#[derive(NetworkWire)]
#[available_operators(op="Foo", op="Bar<Baz>")]
pub struct MyWire<Baz> {
_p: std::marker::PhantomData<Baz>,
}
};
let res = parse_available_operators_attribute(&di);
assert!(res.is_ok());
let (ops, gens) = res.unwrap();
assert_eq!(ops.len(), 2);
assert_eq!(ops[0].path().segments[0].ident.to_string(), "Foo");
assert_eq!(ops[1].path().segments[0].ident.to_string(), "Bar");
assert_eq!(gens.params.len(), 1); }
#[test]
fn test_parse_fail_no_attr() {
info!("test_parse_fail_no_attr: START");
let di: DeriveInput = parse_quote! {
#[derive(NetworkWire)]
pub struct MyWire {}
};
let res = parse_available_operators_attribute(&di);
assert!(res.is_err());
let err = format!("{}", res.err().unwrap());
assert!(err.contains("Missing `#[available_operators(...)]`"), "Got: {}", err);
}
#[test]
fn parse_empty_input() {
info!("parse_empty_input: START");
let tokens = quote::quote! {};
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_ok());
if let Ok(parser) = parse_res {
assert_eq!(parser.items().len(), 0);
}
}
#[test]
fn parse_single_op_ok() {
info!("parse_single_op_ok: START");
let tokens = quote::quote! { op="Foo" };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_ok());
if let Ok(parser) = parse_res {
let items = parser.items();
assert_eq!(items.len(), 1);
assert_eq!(items[0].path().segments.len(), 1);
assert_eq!(items[0].path().segments[0].ident.to_string(), "Foo");
}
}
#[test]
fn parse_multiple_ops_ok() {
info!("parse_multiple_ops_ok: START");
let tokens = quote::quote! { op="Foo", op="Bar<Baz>", op="Qux" };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_ok());
if let Ok(parser) = parse_res {
let items = parser.items();
assert_eq!(items.len(), 3);
assert_eq!(items[0].path().segments[0].ident.to_string(), "Foo");
assert_eq!(items[1].path().segments[0].ident.to_string(), "Bar");
assert_eq!(items[2].path().segments[0].ident.to_string(), "Qux");
}
}
#[test]
fn parse_fails_wrong_keyword() {
info!("parse_fails_wrong_keyword: START");
let tokens = quote::quote! { not_op="Foo" };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_err());
if let Err(err) = parse_res {
let msg = err.to_string();
assert!(
msg.contains("Expected `op`"),
"unexpected error message: {}",
msg
);
}
}
#[test]
fn parse_fails_missing_eq() {
info!("parse_fails_missing_eq: START");
let tokens = quote::quote! { op "Foo" };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_err());
if let Err(err) = parse_res {
let msg = err.to_string();
assert!(
msg.contains("expected `=`"),
"unexpected error message: {}",
msg
);
}
}
#[test]
fn parse_fails_invalid_path() {
info!("parse_fails_invalid_path: START");
let tokens = quote::quote! { op="::" };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_err());
if let Err(err) = parse_res {
let msg = err.to_string();
assert!(
msg.contains("Could not parse `::` as a Path."),
"unexpected error message: {}",
msg
);
}
}
#[test]
fn parse_ok_trailing_comma() {
info!("parse_ok_trailing_comma: START");
let tokens = quote::quote! { op="Foo", };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_ok());
if let Ok(parser) = parse_res {
assert_eq!(parser.items().len(), 1);
assert_eq!(parser.items()[0].path().segments[0].ident.to_string(), "Foo");
}
}
#[test]
fn parse_ok_spacing_variations() {
info!("parse_ok_spacing_variations: START");
let tokens = quote::quote! {
op = "AlphaOp"
, op = "BetaOp" ,
op="GammaOp"
};
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_ok());
if let Ok(parser) = parse_res {
let items = parser.items();
assert_eq!(items.len(), 3);
assert_eq!(items[0].path().segments[0].ident.to_string(), "AlphaOp");
assert_eq!(items[1].path().segments[0].ident.to_string(), "BetaOp");
assert_eq!(items[2].path().segments[0].ident.to_string(), "GammaOp");
}
}
#[test]
fn parse_fails_empty_string() {
info!("parse_fails_empty_string: START");
let tokens = quote::quote! { op="" };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_err());
if let Err(err) = parse_res {
let msg = err.to_string();
assert!(
msg.contains("Could not parse `` as a Path."),
"unexpected error message: {}",
msg
);
}
}
#[test]
fn parse_fails_extra_tokens() {
info!("parse_fails_extra_tokens: START");
let tokens = quote::quote! { op="Foo" something_else };
let parse_res = OperatorItemsParser::parse.parse2(tokens);
assert!(parse_res.is_err());
if let Err(err) = parse_res {
let msg = err.to_string();
assert!(
msg.contains("unexpected token"),
"Got unexpected error message: {}",
msg
);
}
}
}