use proc_macro2::Ident; use quote::format_ident; use syn::{ Attribute, Error, FnArg, Item, ItemFn, ItemMacro, ItemMod, Meta, Pat, Result, ReturnType, Type, Visibility, }; use crate::defs::{ AllowedType, AllowedTypes, ArgSource, Defs, FieldInfo, MicrocodeArg, MicrocodeOp, MicrocodeRes, MicrocodeSubtype, MicrocodeTypeDef, StackInfo, }; use crate::parsing::AllowedTypesMappings; /// Extracts everything we need for the generator to work. pub fn extract_defs(name: Ident, mut module: ItemMod) -> Result { let vis = module.vis.clone(); let docs = extract_docs(&module.attrs); let (allowed_types, ops) = match module.content { Some(ref mut content) => { extract_and_filter_items(&module.ident, &module.vis, &mut content.1)? } None => { return Err(Error::new( module.ident.span(), "define_microcode must be used on an inline module, not one defined \ externally", )); } }; let descriptor_name = format_ident!("{}Descriptor", name); Ok(Defs { module, microcode_type: MicrocodeTypeDef { name, vis, docs, ops, descriptor_name, }, allowed_types, }) } /// Extract the docs from a list of attributes. fn extract_docs(attrs: &[Attribute]) -> Vec { attrs .iter() .filter(|attr| match &attr.meta { Meta::NameValue(nv) => nv.path.is_ident("doc"), _ => false, }) .cloned() .collect() } /// Processes the list of items in the module. /// /// Finds ones tagged `#[microcode(Name)]` or `#[microcode_extern(Name)]` and converts /// them into [`MicrocodeOp`s][MicrocodeOp]. For externs, discards the function defintion /// from the module. For pure-function ops, removes any microcode related tags. fn extract_and_filter_items( mod_name: &Ident, vis: &Visibility, items: &mut Vec, ) -> Result<(AllowedTypes, Vec)> { let mut ops = Vec::with_capacity(items.len()); let mut allowed_types = None; let mut i = items.len(); while i > 0 { i -= 1; match &mut items[i] { Item::Fn(func) => { if let Some(op) = extract_op_from_item(func)? { let subtype = op.subtype; ops.push(op); // Discard externs. if subtype == MicrocodeSubtype::Extern { items.remove(i); } } } Item::Macro(mac) => { if mac.mac.path.is_ident("allowed_types") { if allowed_types.is_some() { return Err(Error::new_spanned( &mac.mac.path, "allowed_types defined more than once.", )); } allowed_types = Some(extract_allowed_types(mac, vis)?); // Discard the allowed_types macro. items.remove(i); } } _ => {} } } let allowed_types = match allowed_types { Some(allowed_types) => allowed_types, None => { return Err(Error::new( mod_name.span(), "define_microcode module must include an allowed_types macro call", )) } }; ops.reverse(); Ok((allowed_types, ops)) } /// Checks if the given item has either `#[microcode(Name)]` or /// `#[microcode_extern(Name)]` and if so returns the `MicrocodeOp` that it defines, /// otherwise returns `None`. If the item defines a microcode op, the attribute marking it /// as such will be removed, as will the #[field] attribute on any of its parameters. fn extract_op_from_item(item: &mut ItemFn) -> Result> { Ok( if let Some((name, subtype)) = extract_microcode_marker(&mut item.attrs)? { let docs = extract_docs(&item.attrs); Some(MicrocodeOp { name, func_name: item.sig.ident.clone(), docs, args: extract_args(item.sig.inputs.iter_mut())?, returns: extract_returns(&item.sig.output)?, subtype, sig: item.sig.clone(), }) } else { None }, ) } /// Reads through the microcode attributes and finds one labeled either `microcode` or /// `microcode_extern`, and extracts the `Ident` from it. fn extract_microcode_marker( attrs: &mut Vec, ) -> Result> { let mut res = None; let mut i = attrs.len(); while i > 0 { i -= 1; match &attrs[i].meta { Meta::Path(path) => { if path.is_ident("microcode") { return Err(Error::new_spanned( path, "#[microcode] marker on requires an argument to specify the name \ of the enum value", )); } if path.is_ident("microcode_extern") { return Err(Error::new_spanned( path, "#[microcode_extern] marker on requires an argument to specify \ the name of the enum value", )); } // Retain all other path attributes. continue; } Meta::List(list) => { let subtype = if list.path.is_ident("microcode") { MicrocodeSubtype::Function } else if list.path.is_ident("microcode_extern") { MicrocodeSubtype::Extern } else { // Not a microcode attribute, don't delete. continue; }; if !res.is_none() { return Err(Error::new_spanned( &list.path, "may only specify one #[microcode(...)] or \ #[microcode_extern(...)] attribute.", )); } res = Some((syn::parse2::(list.tokens.clone())?, subtype)); } Meta::NameValue(nv) => { if nv.path.is_ident("microcode") { return Err(Error::new_spanned( &nv.path, "`#[microcode(...)]` uses a parenthsized argument not `microcode \ = ...` syntax", )); } else if nv.path.is_ident("microcode_extern") { return Err(Error::new_spanned( &nv.path, "`#[microcode_extern(...)]` uses a parenthsized argument not \ `microcode_extern = ... syntax`", )); } // Not a microcode attribute, retain it. continue; } } attrs.remove(i); } Ok(res) } /// Iterates over the arguments to a function and extracts them as [`MicrocodeArg`]. If /// any are field arguments, the `#[field]` attribute is removed. fn extract_args<'a>(inputs: impl Iterator + 'a) -> Result> { inputs .map(|arg| match arg { FnArg::Receiver(recv) => { return Err(Error::new( recv.self_token.span, "microcode funcs must be free functions, not associated functions", )); } FnArg::Typed(arg) => { let mut is_field = false; let mut docs = vec![]; let mut i = arg.attrs.len(); while i > 0 { i -= 1; match &arg.attrs[i].meta { Meta::Path(path) => { if path.is_ident("field") { is_field = true; arg.attrs.remove(i); } } Meta::List(list) => { if list.path.is_ident("field") { return Err(Error::new_spanned( &list.path, "`#[field]` attribute does not take any args", )); } } Meta::NameValue(nv) => { if nv.path.is_ident("field") { return Err(Error::new_spanned( &nv.path, "`#[field]` attribute does not take any args", )); } if nv.path.is_ident("doc") { docs.push(arg.attrs.remove(i)); } } } } // Name of the argument. let name = extract_ident_from_pat(&arg.pat).cloned(); let source = if is_field { match name { Some(ident) => ArgSource::Field(FieldInfo { name: ident.clone(), docs, }), _ => { return Err(Error::new_spanned( &arg.pat, "argument with `#[field]` must use a single ident pattern.", )); } } } else { ArgSource::Stack(StackInfo { name }) }; Ok(MicrocodeArg { ty: arg.ty.as_ref().clone(), source, }) } }) .collect() } /// If the pattern is a single identifier, return it, otherwise return none. fn extract_ident_from_pat(pat: &Pat) -> Option<&Ident> { match pat { Pat::Ident(ident) => Some(&ident.ident), _ => None, } } /// Extract the return types for a microcode operator. For most types, the type is /// produced directly, but for tuples, the tuple is unpacked. fn extract_returns(returns: &ReturnType) -> Result> { Ok(match returns { ReturnType::Default => vec![], ReturnType::Type(_, ty) => match &**ty { Type::Tuple(tuple) => { if tuple.elems.len() == 1 { // This check doesn't work if the tuple is behind a type alias. return Err(Error::new_spanned( tuple, "microcode functions are not allowd to return single-element \ tuples. If you only have one value to return, return it directly.", )); } tuple .elems .iter() .map(|ty| MicrocodeRes { ty: ty.clone() }) .collect() } // non-tuple types are used directly. _ => vec![MicrocodeRes { ty: ty.as_ref().clone(), }], }, }) } /// Extract the allowed types from the allowed_types macro. fn extract_allowed_types(mac: &ItemMacro, vis: &Visibility) -> Result { let mapping: AllowedTypesMappings = syn::parse2(mac.mac.tokens.clone())?; Ok(AllowedTypes { docs: extract_docs(&mac.attrs), name: mapping.name.ident, vis: vis.clone(), types: mapping .types .content .into_iter() .map(|item| AllowedType { docs: extract_docs(&item.attributes), ty: item.ty, name: item.ident, }) .collect(), }) }